Securing Reactjs Application with Firebase: User Authentication and Protected Routes Explained

React Firebase Authentication

blog-image

This blog post is about Firebase authentication using Reactjs where we will build a login system where user can create an account or use their email id to log in. We will be using bootstrap & react-bootstrap as well.

In this blog post, we will learn how to create a protected route, how to set user details in context api, and sign in the user with an email and password using firebase plus Google sign-in.

First of all, let's create a Firebase project. Go to Authentication tap and select Sign-in-method and add Email/Password and Google by clicking Add new provider. Find your SDK setup and configuration and copy it.

Now, it is time to create-react-app. Create a new react app by running the following command npx create-react-app firebase-auth Once the app is up and running let's install all packages we need for this app.

npm i firebase react-router-dom bootstrap react-bootstrap react-google-button 

Create a new fireConfig.js file and paste copied firebase sdk. It should have the following code:

import { initializeApp } from "firebase/app";
import {getAuth} from "firebase/auth";


const firebaseConfig = {
  apiKey: "",
  authDomain: "",
  projectId: "",
  storageBucket: "",
  messagingSenderId: "",
  appId: "",
  measurementId: ""
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);

const auth = getAuth(app);

export {auth};
export default app;

Here, we have exported {auth} from the config file.

Let's quickly set up react-router and create Login and Signup pages.

Add the following code in index.js file:

import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import 'bootstrap/dist/css/bootstrap.min.css'
import "bootstrap/dist/js/bootstrap.bundle.min";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <BrowserRouter>
    <App />
  
    </BrowserRouter>
  </React.StrictMode>
);

Here, we just imported react-bootstrap and BrowserRouter from react-router-dom and wrapped our main App component inside BrowserRouter.

In App.js file add following code:


import React from 'react';

import Navbar from './components/Navbar';
import Login from './components/Login';
import Signup from './components/Signup';
import Home from './components/Home';
import Profile from './components/Profile';
import { Route ,Routes} from "react-router-dom";

function App() {
  return (<>
  
    <Navbar/>
    <Routes>

 <Route  path='/' element = {<Home/>}/>
 
<Route path = "/login" element ={<Login/>} />

<Route path = "/signup" element = {<Signup/>} />


<Route path='/profile' element = {<Profile/>} />

    </Routes>

  
    </>
  );
}

export default App;

In the above code, we have imported Navbar component at the top and set up routes for Home page, Login page, Signup page, and Profile page. We will look into each of these pages in detail latter.

Navbar component will have links for different pages of our application. Let's add the following code in `components/Navbar.js

import React from 'react';
import {Link} from "react-router-dom";

import Container from 'react-bootstrap/Container';
    import Nav from 'react-bootstrap/Nav';
    import Navbar from 'react-bootstrap/Navbar';

    function Navigation() {

      return (<>
        <Navbar collapseOnSelect onSelect expand="lg" bg="dark" variant="dark">
          <Container>

          <Navbar.Toggle  aria-controls="responsive-navbar-nav" />
          <Navbar.Collapse  id="responsive-navbar-nav">

              <Nav   className="me-auto">
              <Link className='nav-link' to = "/">Home</Link>
            <Link  className = 'nav-link 'to = "/Signup">Sign up</Link>
<Link className='nav-link' to = "/login">Log In</Link>
</>
)}

            
  </Nav>
          </Navbar.Collapse>


  
          
          </Container>
        </Navbar>
        </>
      );
    }
    
    


export default Navigation;

navbar

Sign up page

Now, create a folder name components and inside that folder add Signup.js file. Signup.js file will have a form where user can enter their email id and password or user can signup using their Google account directly as well and they will be redirected to the profile page. To allow the user to sign up using email and password, we need createUserWithEmailAndPassword method from firebase/auth and for gmail login we need GoogleAuthProvider and signInWithPopup methods.

import React, { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { createUserWithEmailAndPassword,GoogleAuthProvider,  signInWithPopup } from "firebase/auth";
import { auth } from "../fireConfig";
import {GoogleButton} from "react-google-button"

const Signup = ()=>{
const[disabled,setDisabled] = useState(false);
const navigate = useNavigate();
const [user,setSignUser] = useState({
  email: "",
  password: ""
});

const [error, setError] = useState(null)

const inputHandeler = (e)=>{
  setSignUser({...user,[e.target.name]:e.target.value})
}

const signInWithGoogle = async()=>{
    const provider = new GoogleAuthProvider();
  try{
await signInWithPopup(auth,provider);
navigate("/profile");
setError("");
  } catch(er){
    setError(er.message)
  }
}


const  formHandeler = async (e)=>{
  e.preventDefault();
setDisabled(true);
  if( user.email.trim() === "" || user.password.trim()=== "")
  {setDisabled(false);
    return (setError("All fields are required to fill"))}
try{
   await createUserWithEmailAndPassword(auth, user.email,user.password);

setSignUser({
  email: "",
  password: ""
})
setError(null)
navigate("/profile");

}catch(er){
setError(er.message)
}
setDisabled(false)
}
    return( <>
    <div className="form-container">
      <div className="Auth-form">
        <h3 className="Auth-form-title m-1">Sign Up</h3>
        <div className="text-center m-2">
        Already registered?
              <span className="link-primary btn-group m-2">
              <Link  className = 'btn btn-primary'to = "/login">Sign In</Link>
              </span>
              {error && <div className="”alert error"> {error} </div>}

            </div>

            <form>
        <div className="form-group mt-1">
          <label>Email address</label>
          <input
            type="email" onChange={inputHandeler} value={user.email} name = "email"
            className="form-control mt-1"
            placeholder="Enter email"
          />
        </div>
        <div className="form-group mt-1">
          <label>Password</label>
          <input
          onChange={inputHandeler}
            type="password" value={user.password} name = "password"
            className="form-control  mt-1"
            placeholder="Enter password"
          />
        </div>
        <div className="d-grid gap-2 mt-3">
          <button disabled = {disabled} type="submit"onClick={formHandeler} className="btn btn-primary">
            Submit
          </button>
        </div>
    </form>

              <div className="btn-group">
          <GoogleButton onClick = {signInWithGoogle}/>
         </div>
      </div>
  </div>
  
</>
    )}


export default Signup;

signup component

Login page

Login page will have a form where user can type thier email and password or user can login directly using their Gmail id. To allow user to login we need signInWithEmailAndPassword method from firebase/auth

import React, {useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { signInWithEmailAndPassword, GoogleAuthProvider, signInWithPopup} from "firebase/auth";
import{auth} from "../fireConfig";
import GoogleButton from "react-google-button";

const Login = ()=>{
const[disable,setDisable] = useState(false);
const navigate = useNavigate();
const [error, setError] = useState(null)
const [user,setLoginUser]= useState({
  email:"",
  password:""
})

const changeHandeler = (e)=>{
setLoginUser({...user, [e.target.name]:e.target.value})

}

const googleLogin = async ()=>{
    const provider = new GoogleAuthProvider();
  try{
await signInWithPopup(auth,provider);
navigate("/profile");
setError("");
  } catch(er){
    setError(er.message)
  }
        
}

const formHandeler = async (e)=>{
  e.preventDefault();
setDisable(true);
if(user.email.trim()=== "" || user.password.trim()=== ""){
  setDisable(false)
  return setError("All fields are required to fill");
}

try{

  await signInWithEmailAndPassword(auth,user.email,user.password)

  setLoginUser({email:"",
  password:""})
  setError(null);
  navigate("/profile");
}catch(er){

  setError(er.message);
}
setDisable(false);
}

    return( <div className="form-container">
    
      <div className="Auth-form">
    <form >

        <h3 className="Auth-form-title m-0">Sign In</h3>
        <div className="text-center">
              Not registered yet?
              <span className="link-primary">
              <Link  className = 'nav-link'to = "/Signup">Sign up</Link>
              </span>

            {error && <div className="”alert error"> {error} </div>}
            </div>
        <div className="form-group mt-1">
          <label>Email address</label>
          <input
            type="email"
            name="email"
            value={user.email}
            onChange = {changeHandeler}
            className="form-control mt-1"
            placeholder="Enter email"
          />
        </div>
        <div className="form-group mt-1">
          <label>Password</label>
          <input
          name="password"
          value={user.password}
          onChange = {changeHandeler}
            type="password"
            className="form-control  mt-1"
            placeholder="Enter password"
          />
        </div>
        <div className="d-grid gap-2 mt-3">
          <button type="submit" disabled ={disable} onClick={formHandeler} className="btn btn-primary">
            Submit
          </button>
        </div>
    </form>
    <GoogleButton className="m-2" onClick={googleLogin}/>
      </div>
   
  </div>)

}

export default Login;

login page

Storing and accessing user details using React Context API

We want to access user details inside our components tree. We can achieve this by using context API. Create a new file named AuthContex.js and add following code

import React, {useContext, useState, useEffect} from "react";

import { onAuthStateChanged} from "firebase/auth";
import { auth } from "../../fireConfig";

 const AuthContext = React.createContext();

 const AuthProvider = ({children})=>{
    
  const [user, setUser]= useState(null);
  useEffect(()=>{
  
   const unscribe = onAuthStateChanged(auth,(user)=>{
      setUser(user);
  })
  return unscribe;
  
  },[])


    return(<AuthContext.Provider value = {{user}}>

        {children}
    </AuthContext.Provider>)
}
 const useAuth = ()=>{
    return useContext(AuthContext);
}

export{AuthProvider, useAuth};

Here, We have imported onAuthStateChanged method which allows to subscribe to the current user's authentication state and receive an event if the state changes.

Protected route

We want to protect /profile route. User only can access that route if they are logged in. Create a file ProtectedRoute.js file and add following code.

import React from "react";
import { useAuth } from "./AuthContext";
import { Navigate } from "react-router-dom";

 const ProtectedRoute = ({children})=>{
const {user}= useAuth();
if(!user){
    return <Navigate to="/" replace ={true}/>
}

return children;
}

 const RedirectRoute = ({children})=>{
const {user} = useAuth();
if(user){
    return <Navigate to = "/profile" replace = {true}/>
}

return children;
}
export {ProtectedRoute, RedirectRoute};

In the above code, we have exported two functions ProtectedRoute which will simply redirect user to home page if user is not logged in and RedirectRoute which will redirect user to profile page if they are logged in and try to go to Login page or Sign up page. Now we have to modify our App.js file.

import {AuthProvider} from "./components/contexts/AuthContext";
import {ProtectedRoute, RedirectRoute} from "./components/contexts/ProtectedRoute";

function App() {
  return (<>
  
<AuthProvider>
    <Navbar/>
    
    <Routes>
      <Route  path='/' element = {<Home/>}/>
<Route path = "/login" element ={<RedirectRoute>
<Login/>
</RedirectRoute>
} />

<Route path = "/signup" element = {<RedirectRoute>
  <Signup/>
</RedirectRoute>
  } 
   />

<Route path='/profile' element = {

<ProtectedRoute>
  <Profile/>
</ProtectedRoute>

  }>
   
    </Route> 
    </Routes>

    </AuthProvider>
    </>
  );
}

export default App;

In the above code, we have wrapped all components inside AuthProvider so that we can access user details in every component we want to. and Profile component is wrapped inside ProtectedRoute which can be accessed only if the user is logged in.

Profile page

In the profile page we will display Logout button. user can log out by clicking this button and we will display current user email id as well. We have to import SignOut method from firebase/auth which will allow user to log out.

import React, {useState} from "react";
import {useAuth} from "../components/contexts/AuthContext";
import { Button, Modal } from "react-bootstrap";
import { signOut } from "firebase/auth";
import { auth } from "../fireConfig";

const Profile = ()=>{
  const {user} = useAuth();
   const [show, setShow] = useState(false);
   const handleClose = () => setShow(false);
   const handleShow = () => setShow(true);

   const logoutHandler = async ()=>{
       try{
       await signOut(auth);
       }catch(er){
          console.log(er.message);
       }
       }
       
   return(<>
<div className="container">
  <h2>
  {user.email}
   </h2> 
   <Button className="btn btn-secondary" onClick={handleShow}> LogOut</Button>
<Modal  show={show} onHide={handleClose}>
    <Modal.Header closeButton>
      <Modal.Title> User logOut</Modal.Title>
    </Modal.Header>
    <Modal.Body>Do you want to logout?</Modal.Body>
    <Modal.Footer>
      <Button variant="secondary" onClick={handleClose}>
        No
      </Button>
      <Button variant="primary" onClick={logoutHandler}>
        Yes
      </Button>
    </Modal.Footer>
  </Modal>

     </div>
   </>)

}

export default Profile;

profile-page

I have modified Navbar component. If user is logged in, link to the Profile page will be seen. Links for Login and Sign up will be hidden. For that add following code in Navbar.js file

 import {useAuth} from "../components/contexts/AuthContext";
    function Navigation() {
const {user} = useAuth();

      return (<>
        <Navbar collapseOnSelect onSelect expand="lg" bg="dark" variant="dark">
          <Container>
                {user && <Navbar.Brand>{user.displayName}</Navbar.Brand>}

          <Navbar.Toggle  aria-controls="responsive-navbar-nav" />
          <Navbar.Collapse  id="responsive-navbar-nav">

              <Nav   className="me-auto">
              <Link className='nav-link' to = "/">Home</Link>
              {user ?  (<Link  className = 'nav-link 'to = "/profile">Profile</Link>):(<><Link  className = 'nav-link 'to = "/Signup">Sign up</Link>
<Link className='nav-link' to = "/login">Log In</Link>
</>
)}

            
  </Nav>
          </Navbar.Collapse>
    </Container>
        </Navbar>
        </>
      );
    }
    
    


export default Navigation;

Conclusion

In this blog post, we learned the process of implementing user authentication in a React application using Firebase. We learned to integrate protected routes and Context API.

Comments

No comments