In this tutorial , we’ll be setting up our own Firebase project through the Firebase Console to setup user registration and authentication in our React application. Firebase simplifies the process of user authentication by providing all necessary functions and SDK to be integrated in your project and allowing you to focus on other parts of the development process.
Let’s get started by creating our application using Create React App and installing the Firebase, managing User State with React Context API and React Router dependencies by running the following commands but make sure you have the necessary requirements for authenticating with Firebase in React
- Node.js installed
- Code editor ( Visual Studio Code preferably )
- Google account which will be to used in Firebase
- Basic knowledge of React
npx create-react-app react-auth-form
cd react-auth-form
npm install firebase react-router-dom
Create a Firebase account and add a "New Project".
Follow the provided prompts to complete the setup and you'll end up here:
To add Firebase to your app use the web option (</>).
To complete the Firebase setup we need to specify an authentication method. There are quite a number of options of methods available but for this tutorial we'll be using the email and password method. Navigate to "Authentication" -> "Sign-in Method" and change the "Email/Password" status setting to "Enabled".
Firebase config
Create a new file in the following location - src/firebase.js.
This file import's the Firebase SDK and contains the Firebase configuration settings and setting all inputs into your .env file with the standard variable names for secured purposes:
import { initializeApp } from "firebase/app"
import { getAuth } from "firebase/auth"
import "firebase/auth"
import { getFirestore } from '@firebase/firestore';
const firebaseConfig = {
apiKey: process.env.REACT_APP_FIREBASE_KEY,
authDomain: process.env.REACT_APP_FIREBASE_DOMAIN,
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_FIREBASE_SENDER_ID,
appId: process.env.REACT_APP_FIREBASE_APP_ID,
measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENT_ID
};
export const app = initializeApp(firebaseConfig)
export const auth = getAuth(app)
export const db = getFirestore(app);
Copy these setting from "Project Settings" - "General" in the Firebase console.
Register users using React-hook-form
Create a new file in the following location - src/components/Register.js.
This component contains a sign up form so users can create accounts using react-hook-form:
To install React Hook Form, run the following command:
npm install react-hook-form
First, import the useForm Hook from the installedreact-hook-form package:
import { useForm } from "react-hook-form";
Then, inside your component, use the Hook as follows to validate errors, submit data and register:
const { handleSubmit, formState: { errors }, trigger, register, watch } = useForm();
To apply validations to a field in the form input, you can pass validation parameters to the register method. Validation parameters are similar to the existing HTML form validation standard.
These validation parameters include the following properties:
required indicates if the field is required or not. If this property is set to true, then the field cannot be empty
minlength and maxlength set the minimum and maximum length for a string input value
min and max set the minimum and maximum values for a numerical value
type indicates the type of the input field; it can be email, number, text, or any other standard HTML input types
pattern defines a pattern for the input value using a regular expression
Now lets collect data and register with firebase
import React from 'react'
import { createUserWithEmailAndPassword } from 'firebase/auth';
import { useForm } from 'react-hook-form';
const Register = ({ history }) => {
const {
handleSubmit,
formState: { errors },
trigger,
register,
watch
} = useForm();
async function onhandleSubmit(data) {
//console.log(data)
try {
await createUserWithEmailAndPassword(
auth, data.email, data.password, data.name)
history.push("/");
alert ("User Created Successfully")
} catch (error) {
console.log(error)
alert ("User created failed")
alert(error);
}
}
return (
<div>
<Form onSubmit={handleSubmit(onhandleSubmit)}>
<h5>Create an account</h5>
<div>
<div>
<label>Your email address</label>
<input
id="email"
name="email"
type= 'email'
required={true}
{...register("email", {
required: "Email is Required!!!" ,
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: "Invalid email address",
}})}
error={Boolean(errors.email)}
onKeyUp={() => {trigger("email")}}
></input>
{errors.email && (
<small className="text-danger">{errors.email.message}</small>
)}
</div>
<div>
<label>Your password</label>
<input
name='password'
id="password"
type= 'password'
autoComplete='off'
className={`form-control ${errors.password && "invalid"}`}
required={true}
{...register("password", {
required: "You must specify a password",
pattern: {
value: '^(?=.*?[A-Z])(?=(.*[a-z]){1,})(?=(.*[\d]){1,})(?=(.*[\W]){ 1,})(?!.*\s).{8,}$',
message: "Password should contain at least one number and one special character"
},
minLength: {
value: 8,
message: "Password must be more than 8 characters"
},
maxLength: {
value: 20,
message: "Password must be less than 20 characters"
},
})}
onKeyUp={() => {trigger("password")}}
error={Boolean(errors.password)}
></input>
{errors.password && (
<small className="text-danger">{errors.password.message}</small>
)}
</div>
<div>
<label>Confirm your password</label>
<input
id="confirmPassword"
name="confirmPassword"
type='password'
{...register( 'confirmPassword', {
validate: value =>
value === watch("password", "") || "The passwords do not match"
})}
autoComplete='off'
onPaste={(e) =>{
e.preventDefault();
return false
}}
error={Boolean(errors.confirmPassword)}
className={`form-control ${errors.confirmPassword && "invalid"}`}
required={true}
onKeyUp={() => {trigger("confirmPassowrd")}}
/>
{errors.confirmPassword && (
<small className="text-danger">{errors.confirmPassword.message} </small>
)}
</div>
<div>
<label>Your full name</label>
<input
name='name'
type="name"
className={`form-control ${errors.name && "invalid"}`}
required={true}
defaultValue=""
{...register("name", { required: "Fullname is Required!!!" })}
onKeyUp={() => {trigger("name")}}/>
{errors.name && (
<small className="text-danger">Fullname is Required!!!</small>
)}
</div>
<div>
<button>Create an account</button>
</div>
</div>
</Form>
</div>
)}
export default withRouter(Register)
Managing User State with React Context API
Context API is a way to share data with components at any level of the React component tree without having to pass it down as props. Since a user might be required by a different component in the tree, using the Context API is great for managing the user state.
Before we start using the Context API, there are a few things we need to set up:
- Create a context object using the createContext() method
- Pass the components we want to share the user state with as children of Context.Provider
- Pass the value we want the children/consuming component to access as props to Context.Provider
Let’s get to it. In the src directory, create an AuthContext.js file and add the following lines of code to it:
import React, { useEffect, useState, useContext } from "react";
import { auth } from "./api";
import {
createUserWithEmailAndPassword,
onAuthStateChanged,
sendPasswordResetEmail,
signInWithEmailAndPassword } from "firebase/auth";
export const AuthContext = React.createContext();
export function useAuth() {
return useContext(AuthContext)
}
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
const [pending, setPending] = useState(true);
function signup(email, password, name) {
return createUserWithEmailAndPassword(auth, email, password, name)
}
function login(email, password) {
return signInWithEmailAndPassword(auth ,email, password)
}
function logout() {
return auth.signOut()
}
function resetPassword(email) {
return sendPasswordResetEmail(auth, email)
}
function updateEmail(email) {
return currentUser.updateEmail(email)
}
function updatePassword(password) {
return currentUser.updatePassword(password)
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, user => {
setCurrentUser(user)
setPending(false)
});
return unsubscribe
}, []);
if(pending){
return <>Loading...</>
}
const value = {
currentUser,
login,
signup,
logout,
resetPassword,
updateEmail,
updatePassword
}
return (
<AuthContext.Provider
value={value}
>
{children}
</AuthContext.Provider>
);
};
Create a new file in the following location — src/components/Login.js.
This component contains the log in form so users can sign into their account:
import React, { useContext } from "react";
import { Redirect } from "react-router-dom";
import { AuthContext } from "./Auth";
import firebaseConfig from "../firebase.js";
import { useForm, Controller } from 'react-hook-form';
const Login = () => {
const {
handleSubmit,
control,
formState: { errors },
} = useForm();
const submitHandler = async ({ email, password }) => {
await try {
firebaseConfig.auth().signInWithEmailAndPassword(email, password);
} catch (error) {
alert(error);
}
};
const { currentUser } = useContext(AuthContext);
if (currentUser) {
return <Redirect to="/dashboard" />;
}
return (
<>
<h1>Log In</h1>
<form onSubmit={handleSubmit(submitHandler)}>
<label for="email">Email</label>
<input
id="email"
name="email"
type= 'email'
required={true}
{...register("email", {
required: "Email is Required!!!",
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: "Invalid email address",
}})}
error={Boolean(errors.email)}
onKeyUp={() => {trigger("email")}}
></input>
{errors.email && (
<small className="text-danger">{errors.email.message}</small>
)}
</div>
<div>
<label>Your password</label>
<input
name='password'
id="password"
type= 'password'
autoComplete='off'
className={`form-control ${errors.password && "invalid"}`}
required={true}
{...register("password", {
required: "You must specify a password",
pattern: {
value: '^(?=.*?[A-Z])(?=(.*[a-z]){1,})(?=(.*[\d]){1,})
(?=. (.*. [\W]){ 1,})(?!.*\s).{8,}$',
message: "Password should contain at least one number
and one special character"
},
minLength: {
value: 8,
message: "Password must be more than 8 characters"
},
maxLength: {
value: 20,
message: "Password must be less than 20 characters"
},
})}
onKeyUp={() => {trigger("password")}}
error={Boolean(errors.password)}
></input>
{errors.password && (
<small className="text-danger">
{errors.password.message}
</small>
)}
</div>
<button type="submit">Submit</button>
</form>
</>
);
};
export default Login;
If the current user is already logged in they’ll get redirected to the home page. Otherwise we capture the form input on submit and send the details to the Firebase Authentication signInWithEmailAndPassword method.
Home page
Create a new file in the following location — src/components/Home.js.
This component contains content that can only be viewed by authenticated users:
import React from "react";
import { useAuth } from '../../services/auth';
import { useState } from 'react';
import { useHistory } from 'react-router-dom';
const Home = () => {
const [error, setError] = useState("")
const { currentUser, logout } = useAuth()
const history = useHistory()
async function handleLogout() {
setError("")
try {
await logout()
history.push("/login")
} catch {
setError("Failed to log out")
}
}
return (
<div>
<h1>Welcome</h1>
<p>This is the Home Page, if you can see this you're logged in.
</p>
<button onClick={handleLogout}>Sign out</button>
</div>
);
};
export default Home;
If this page is accessed by a non-authenticated user the browser will re-direct to the login page. If the user is authenticated we display the private content. We’ve also included a button so users can sign out of their account.
Pulling it all together in App.js
Modify the App.js file to include the following:
import './App.css';
import {
BrowserRouter as Router,
Route,
} from "react-router-dom";
import Register from "./pages/Register/Register";
import Login from "./pages/Login/Login";
import { AuthProvider } from "./services/auth";
import PrivateRoute from "./PrivateRoute";
import Profile from "./pages/Profile/Profile";
import Home from "./pages/Home/Home";
function App() {
return (
<AuthProvider>
<Router>
<div>
<PrivateRoute exact path="/" component={Home} />
<Route path="/login" component={Login} />
<Route path="/register" component={Register} />
</div>
</Router>
</AuthProvider>
)}
export default App;
You can now run npm start to start the application and test out the registration and authentication process. Once the sign up form has been submitted you can browse to “Authentication” - “Users” in the Firebase console to confirm registration was successful and manage user accounts.
You now know how to authenticate users in your React applications using Firebase. If you would like to learn more about Firebase Authentication, I’d suggest checking out the official guide.
Conclusion
In this tutorial, we have learned how to use of the Firebase Authentication to build user registration and authentication service in React using React Hook Form for form validation.
References
- Get Started with Firebase Authentication on Websites (Firebase documentation)
- Form Validation using React Hook Form (React Hook Form documentation)
- Manage Users in Firebase (Firebase Users documentation)
- Managing User State with React Context API
Top comments (5)
Can you share the github repo ? There are a lot of thing that can be hard to follow, also as a bit of feedback highlight the examples properly can help con readability.
Nice post, cheers :)
How do you make authentication in React?
Vashikaran Specialist In Faridabad
Can you please be more specific on your question ?
Thank you for writing this blog post. ❤️
Thanks for your post