Published

- 14 min read

AuthGuard React Router V6

img of AuthGuard React Router V6

Firebase, ReactJs, React Router — Protected Routes

Integrating authentication functionality in ReactJs with React Router was a bit complicated, The main reason is there is not any pre-define structure for integration. You can define your own structure. So here I have show you some useful stuff for Authentication AuthGuard.

As usual there are 3 kind of routes in website,

First one, those are under authentication, Like /profile, /dashboard or /settings etc…

Second one, where we user must not Logged In, if you are logged in then you can not visit that page, Like /login, /register & /forgot-password etc…

Last one is, it does not matter you are logged in or not, you can visit any how Like /blogs, /about-us etc…

Actually, In this integration I have create two Guards for routes,

First one is AuthGuard, used to check the user must logged in.

Second one is UnAuthGuard, used to check the user must not logged in.

Hope so, you understand the base concept, what we are going to implement, we have used here Firebase dependency for authentication, so you also learn how to implement easily.

Lets start….

What you will see…

  • Create React App
  • Integrate Routing and Guard Components
  • Design Login, Register, Welcome Page
  • Setup Firebase Project
  • Integrate Firebase & Authentication
  • Check The Progress

Create React App

  • We are using Node 16.
   > npx create-react-app react-router-auth-guard

Install Basic Dependencies

   > npm i react-router-dom firebase

Integrate Routing and Guard Components

  • Now we have react app ready & we have to integrate Routing and Guard

Lets Start from Guard Components

Create folder /src/guards

Create files /src/guards/AuthGuards.js & /src/guards/UnAuthGuards.js

AuthGuard.js

   import React, { useEffect } from 'react'

const AuthGuard = ({ component }) => {
	useEffect(() => {
		console.log('Auth Guard')
	}, [])

	return <React.Fragment>{component}</React.Fragment>
}

export default AuthGuard

UnAuthGuard.js

   import React, { useEffect } from 'react'

const UnAuthGuard = ({ component }) => {
	useEffect(() => {
		console.log('UnAuth Guard')
	}, [component])

	return <React.Fragment>{component}</React.Fragment>
}

export default UnAuthGuard

In the Guard component you can check we have just console log the guard, and return the component as it, for now it is enough for routing implement. After this routing we will writer the whole integration.

Need Pages Components

Now we have to create pages component for using in routing. We will have 3 pages. /login, /register & /welcome

  • Create folder /src/pages
  • Create files /src/pages/Login.js, /src/pages/Register.js & /src/pages/Welcome.js

Login.js

   import React, { useEffect } from 'react'

const Login = () => {
	useEffect(() => {
		console.log('Login')
	}, [])

	return <div> Login Form </div>
}

export default Login

Register.js

   import React, { useEffect } from 'react'

const Register = () => {
	useEffect(() => {
		console.log('Register')
	}, [])
	return <div> Register Form </div>
}

export default Register

Welcome.js

   import React, { useEffect } from 'react'

const Welcome = () => {
	useEffect(() => {
		console.log('Welcome')
	}, [])
	return <div> Welcome </div>
}

export default Welcome

We have return simple text for page component.

Routes Integration

Here we will create 2 separate list of routes and will add that routes to application routing.

Start from AuthRoutes

  • Create folder /src/routes
  • Create files /src/routes/AuthRoutes.js

AuthRoutes.js

   import React from 'react'
import { Route } from 'react-router-dom'
import AuthGuard from '../guards/AuthGuard'
import Welcome from '../pages/Welcome'

const AuthRoutes = [
	<Route key='Welcome' path='/' element={<AuthGuard component={<Welcome />} />} />
]

export default AuthRoutes

Currently we have only /welcome route under auth. So, this is the only one in list.

Second One UnAuthRoutes

  • Create folder /src/routes
  • Create files /src/routes/UnAuthRoutes.js

UnAuthRoutes.js

   import React from 'react'
import { Route } from 'react-router-dom'
import UnAuthGuard from '../guards/UnAuthGuard'
import Login from '../pages/Login'
import Register from '../pages/Register'

const UnAuthRoutes = [
	<Route key='Login' path='/login' element={<UnAuthGuard component={<Login />} />}></Route>,
	<Route key='Register' path='/register' element={<UnAuthGuard component={<Register />} />}>
		{' '}
	</Route>
]

export default UnAuthRoutes

As you see, we have 2 routes in unauth list /login & /register.

Also, check that we have attacked pages component in each routes item.

Add AuthRoutes & UnAuthRoutes to App component

App.js

   import './App.css'
import { BrowserRouter, Routes } from 'react-router-dom'
import AuthRoutes from './routes/AuthRoutes'
import UnAuthRoutes from './routes/UnAuthRoutes'

function App() {
	return (
		<div className='App'>
			<BrowserRouter>
				<Routes>
					{AuthRoutes}
					{UnAuthRoutes}
				</Routes>
			</BrowserRouter>
		</div>
	)
}

export default App

Yeah!! We have setup base. Lets test it……

Here We Go….

You must see these page, by calling below urls

Login form 1 Register form 1 Welcome Page 1

Design Login, Register, Welcome Page

Now, we are ready to start next. we have to design our pages. Don’t worry if you do not need to design your own just follow the steps. Also I am a good designer.

We need Material UI in our React setup and Notification.

Install Packages

   > npm install @mui/material @emotion/react @emotion/styled
> npm install @mui/icons-material
> npm install notistack

We can use Material UI component directly importing them but we need to Add SnackbarProvider in src/index.js for message popup

index.js

   import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { SnackbarProvider } from 'notistack';
import { Slide } from '@mui/material';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <SnackbarProvider
      anchorOrigin={{
        vertical: "top",
        horizontal: "right"
      }}
      TransitionComponent={Slide}
      maxSnack={3}
    >
      <App />
    </SnackbarProvider>
  </React.StrictMode>
);

reportWebVitals();

Lets add some Fonts & Style

   @import url('https://fonts.googleapis.com/css?family=Montserrat:400,800');
html, body, 
body > div, 
body > div > .App
{
 height: 100%;
 font-family: 'Montserrat', sans-serif;
}
.page-container{
 display: flex;
 align-items: center;
 justify-content: center;
 height: 100%;
}
.page-block{
 background-color: #fff;
 padding: 30px 25px;
 border-radius: 10px;
 box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22);
 position: relative;
 overflow: hidden;
}
.page-heading{
 font-size: x-large;
 text-transform: capitalize;
 margin-bottom: 15px;
}

Design Form for Login Page — Open Login.js

Login.js

   import React, { useState } from 'react';
import { Button, Grid, IconButton, InputAdornment, TextField } from "@mui/material";
import { Visibility, VisibilityOff } from "@mui/icons-material";

const Login = () => {
  const [passwordVisibility, setPasswordVisibility] = useState(false);

  return (
    <Grid container className="page-container">
      <Grid item md={4} sm={6} xs={11} className="page-block">
        <span className="page-heading"> Login </span>
        <form className="mb-4" >
          <TextField variant="standard" margin="normal" fullWidth
            label="Email Address" name="email" autoFocus />
          <TextField variant="standard" margin="normal"
            label="Password*" type={passwordVisibility ? "text" : "password"} fullWidth name="password"
            InputProps={{
              endAdornment: (
                <InputAdornment position="end">
                  <IconButton edge="end" tabIndex="-1"
                    onClick={e => setPasswordVisibility(!passwordVisibility)} >
                    {passwordVisibility ? <VisibilityOff /> : <Visibility />}
                  </IconButton>
                </InputAdornment>
              ),
            }}
          />
          <Button type="submit" fullWidth variant="contained" color="primary" > Log in </Button>

        </form>
      </Grid>
    </Grid>)
}

export default Login;

Design Form for Register Page — Open Register.js

Register.js

   import { Visibility, VisibilityOff } from '@mui/icons-material';
import { Button, Grid, IconButton, InputAdornment, TextField } from '@mui/material';
import React, { useState } from 'react';

const Register = () => {
  const [passwordVisibility, setPasswordVisibility] = useState(false);

  return (
    <Grid container className="page-container">
      <Grid item md={4} sm={6} xs={11} className="page-block">
        <span className="page-heading"> Register </span>
        <form className="mb-4" >
          <TextField variant="standard" margin="normal" fullWidth
            label="Full Name" name="full_name" autoFocus />
          <TextField variant="standard" margin="normal" fullWidth
            label="Email Address" name="email" autoFocus />
          <TextField variant="standard" margin="normal"
            label="Password*" type={passwordVisibility ? "text" : "password"} fullWidth name="password"
            InputProps={{
              endAdornment: (
                <InputAdornment position="end">
                  <IconButton edge="end" tabIndex="-1"
                    onClick={e => setPasswordVisibility(!passwordVisibility)} >
                    {passwordVisibility ? <VisibilityOff /> : <Visibility />}
                  </IconButton>
                </InputAdornment>
              ),
            }}
          />
          <Button type="submit" fullWidth variant="contained" color="primary" > Create Account </Button>

        </form>
      </Grid>
    </Grid>)
}

export default Register;

Design Form for Welcome Page — Open Welcome.js

Welcome.js

   import { Grid } from '@mui/material';
import React, { useState } from 'react';

const Welcome = () => {

  const [name, setName] = useState("Romik Makavana");

  return (
    <Grid container className="page-container">
      <Grid item md={4} sm={6} xs={11} className="page-block">
        <p className="page-heading">
          Welcome
          <br />
          <span>{name}...</span>
        </p>
      </Grid>
    </Grid>)

}

export default Welcome;

So, Design is completed, Let check how it looks.

Here We Go…

Login form 1 Register form 2 Welcome page 2

Setup Firebase Project

Design is pretty nice, Now we have to setup Firebase project and Integrate in our React app.

Open https://console.firebase.google.com/

Click on “Create a project”

Firebase Create Project

Enter Project name and Continue

Enter Project name

Disable Google analytics and Create Project

Disable Google Analytics

Need to enable Authentication functionality

Firebase Authentication

Click on “Get Started” Select “Email/Password”

Select Email Password

Enable Email/Password only & Save

Enable Email Password Only

Yeah, We have set up firebase project and we are going to integrate in our React app,

Create /src/firebase-config.js

   const FIREBASE_CONFIG = {
    apiKey: "",
    authDomain: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: "",
    appId: ""
}

export default FIREBASE_CONFIG;

Need to get firebase config for integration in our react app

firebase config integration

Enter Project name and Register App, No need to select Hosting option

Project Name Register App

Copy this detail and set to src/firebase-config.js

Copy Config Details

Now, we are at final step. Also, important one.

Integrate Firebase & Authentication

Create folder /src/services
Create file /src/services/auth.js

   import { initializeApp } from "@firebase/app";
import { getAuth, signInWithEmailAndPassword, createUserWithEmailAndPassword, updateProfile } from "firebase/auth";
import FIREBASE_CONFIG from "../firebase-config";

initializeApp(FIREBASE_CONFIG);

const AuthService = {};

AuthService.register = (name, email, password) => {
  const fauth = getAuth();

  return new Promise((resolve, reject) => {
    createUserWithEmailAndPassword(fauth, email, password)
      .then((userCredential) => {
        updateProfile(fauth.currentUser, {
          displayName: name
        }).then(() => {
          resolve({ status: true, message: "Register successfully." });
        }).catch((error) => {
          resolve({ status: false, message: "error.message" });
        });
      })
      .catch((error) => {
        let message = "Something Went Wrong."
        if(error && error.code && error.code == "auth/email-already-in-use"){
          message = "Email already used.";
        }
        resolve({ status: false, message: message });
      });
  });

}

AuthService.user = false;
AuthService.getProfile = (hard = false) => {
    return new Promise(async (res, rej) => {

        const fauth = getAuth();

        await fauth.onAuthStateChanged((user) => {
            if (user) {
                res(user);
            } else {
                res(false);
            }
        });

    });
}

export default AuthService;

Above we have created Auth Service file, all the core logic is written here for authentication. You can see the AuthService.register & AuthService.getProfile Method.

Integrate Register & Guard functionality

We have setup AuthService. Now, we have to implment this in Register and Guard. Edit Register.js

   import { Visibility, VisibilityOff } from '@mui/icons-material';
import { Button, Grid, IconButton, InputAdornment, TextField } from '@mui/material';
import { useSnackbar } from 'notistack';
import React, { useState } from 'react';
import { NavLink, useNavigate } from 'react-router-dom';
import AuthService from '../services/auth';

const Register = () => {
  const [passwordVisibility, setPasswordVisibility] = useState(false);
  const [processing, setProcessing] = useState(false);
  const { enqueueSnackbar, closeSnackbar } = useSnackbar();
  let navigate = useNavigate();

  const [form, setForm] = useState({
    full_name: { value: "" },
    email: { value: "" },
    password: { value: "" }
  });

  const handleChange = (e) => {
    let _form = { ...form };
    _form[e.target.name].value = e.target.value;
    setForm(_form);
  }

  const submitForm = async (e) => {
    e.preventDefault();

    if (form.email.value && form.password.value) {
      setProcessing(true);

      try {
        let data = await AuthService.register(form.full_name.value, form.email.value, form.password.value);
        if (data.status) {
          setProcessing(false); navigate(`/`);
          enqueueSnackbar(data.message, { variant: "success", autoHideDuration: '3s' });
        } else {
          setProcessing(false);
          enqueueSnackbar(data.message, { variant: "error", autoHideDuration: '3s' });
        }
      }
      catch (e) {
        setProcessing(false);
        enqueueSnackbar("Something went wrong.", { variant: "error", autoHideDuration: '3s' });
      }
    } else {
      enqueueSnackbar("All fields are required.", { variant: "error", autoHideDuration: '3s' });
    }

  }


  return (
    <Grid container className="page-container">
      <Grid item md={4} sm={6} xs={11} className="page-block">
        <span className="page-heading"> Register </span>
        <form className="mb-4" onSubmit={submitForm}>
          <TextField variant="standard" margin="normal" fullWidth
            label="Full Name" name="full_name" autoFocus
            value={form.full_name.value} onChange={handleChange}
          />
          <TextField variant="standard" margin="normal" fullWidth
            label="Email Address" name="email"
            value={form.email.value} onChange={handleChange}
          />
          <TextField variant="standard" margin="normal"
            label="Password*" type={passwordVisibility ? "text" : "password"} fullWidth name="password"
            onChange={handleChange}
            value={form.password.value}
            InputProps={{
              endAdornment: (
                <InputAdornment position="end">
                  <IconButton edge="end" tabIndex="-1"
                    onClick={e => setPasswordVisibility(!passwordVisibility)} >
                    {passwordVisibility ? <VisibilityOff /> : <Visibility />}
                  </IconButton>
                </InputAdornment>
              ),
            }}
          />
          <Button type="submit" fullWidth variant="contained" color="primary" disabled={processing}>
            {processing ? "Processing..." : "Create Account"}
          </Button>
          <p>
            <NavLink to={'/login'}>Already have an account ?</NavLink>
          </p>

        </form>
      </Grid>
    </Grid>)
}

export default Register;

Edit AuthGuard.js

   import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import AuthService from '../services/auth';

const AuthGuard = ({ component }) => {
  const [status, setStatus] = useState(false);
  const navigate = useNavigate();

  useEffect(() => {
    checkToken();
  }, [component]);

  const checkToken = async () => {
    try {
      let user = await AuthService.getProfile(true);
      if (!user) {
        navigate(`/login`);
      }
      setStatus(true);
      return;
    } catch (error) {
      navigate(`/login`);
    }
  }

  return status ? <React.Fragment>{component}</React.Fragment> : <React.Fragment></React.Fragment>;
}

export default AuthGuard;

Edit UnAuthGuard.js

   import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import AuthService from '../services/auth';

const UnAuthGuard = ({ component }) => {
    const [status, setStatus] = useState(false);
    const navigate = useNavigate();

    useEffect(() => {
        checkToken();
    }, [component]);

    const checkToken = async () => {
        try {
            let user = await AuthService.getProfile();
            if (!user) {
                localStorage.removeItem("token")
            } else {
                navigate(`/`);
            }
            setStatus(true);
        } catch (error) {
            navigate(`/`);
        }
    }

    return status ? <React.Fragment>{component}</React.Fragment> : <React.Fragment></React.Fragment>;
}

export default UnAuthGuard;

Integrate Welcome Page Functionality

Lets, integrate AuthService in Welcome page and display there who is logged In. Also, we need to display there logout btn for logout user.

Add logout function to src/services/auth.js

   AuthService.logout = async () => {
  return new Promise((resolve) => {
    const fauth = getAuth();
    fauth.signOut().then(() => {
      resolve({ status: true, message: "Logged out successfully." });;
    }).catch(err => {
      resolve({ status: true, message: "Logged out successfully." });
    });
  })
}

Edit Welcome.js

   import { PowerSettingsNew } from '@mui/icons-material';
import { Button, Grid } from '@mui/material';
import { useSnackbar } from 'notistack';
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import AuthService from '../services/auth';

const Welcome = () => {

  const [name, setName] = useState("");
  const [processing, setProcessing] = useState(false);
  const { enqueueSnackbar, closeSnackbar } = useSnackbar();
  let navigate = useNavigate();

  useEffect(() => {
    AuthService.getProfile().then(user => {
      if (user) {
        setName(user.displayName);
      }
    });
  }, []);

  const logout = async () => {
    setProcessing(true);

    try {
      let data = await AuthService.logout();
      if (data.status) {
        setProcessing(false); navigate(`/login`);
        enqueueSnackbar(data.message, { variant: "success", autoHideDuration: '3s' });
      } else {
        setProcessing(false);
      }
    }
    catch (e) {
      setProcessing(false);
      enqueueSnackbar("Something went wrong.", { variant: "error", autoHideDuration: '3s' });
    }
  }

  return (
    <Grid container className="page-container">
      <Grid item md={4} sm={6} xs={11} className="page-block">
        <p className="page-heading">
          Welcome
          <br />
          <span>{name}...</span>
        </p>
        <br />
        <Button variant="contained" color='error' disabled={processing} onClick={logout}>
          <PowerSettingsNew /> &nbsp; {processing ? "Processing...." : "Logout"}
        </Button>
      </Grid>
    </Grid>)

}

export default Welcome;

Integrate Login Page Functionality

So, this is the last one. We had integrate Register, Welcome, Guard and it final one.

Add login frunction to src/services/auth.js

   AuthService.login = (email, password) => {
  const fauth = getAuth();

  return new Promise((resolve, reject) => {
    signInWithEmailAndPassword(fauth, email, password).then((user) => {
      if (user) {
        resolve({ status: true, message: "Login successfully." });
      } else {
        resolve({ status: false, message: "Incorrect Email or Password." });
      }
    }).catch(err => {
      resolve({ status: false, message: "Incorrect Email or Password." });
    });
  });

}

Edit Login.js

   import React, { useState } from 'react';
import { Button, Grid, IconButton, InputAdornment, TextField } from "@mui/material";
import { Visibility, VisibilityOff } from "@mui/icons-material";
import { useSnackbar } from 'notistack';
import { NavLink, useNavigate } from 'react-router-dom';
import AuthService from '../services/auth';

const Login = () => {
  const [passwordVisibility, setPasswordVisibility] = useState(false);
  const [processing, setProcessing] = useState(false);
  const { enqueueSnackbar, closeSnackbar } = useSnackbar();
  let navigate = useNavigate();

  const [form, setForm] = useState({
    email: { value: "" },
    password: { value: "" }
  });

  const handleChange = (e) => {
    let _form = { ...form };
    _form[e.target.name].value = e.target.value;
    setForm(_form);
  }

  const submitForm = async (e) => {
    e.preventDefault();

    if (form.email.value && form.password.value) {
      setProcessing(true);

      try {
        let data = await AuthService.login(form.email.value, form.password.value);
        if (data.status) {
          setProcessing(false); navigate(`/`);
          enqueueSnackbar(data.message, { variant: "success", autoHideDuration: '3s' });
        } else {
          setProcessing(false);
          enqueueSnackbar(data.message, { variant: "error", autoHideDuration: '3s' });
        }
      }
      catch (e) {
        console.log("e",e);
        setProcessing(false);
        enqueueSnackbar("Something went wrong.", { variant: "error", autoHideDuration: '3s' });
      }
    } else {
      enqueueSnackbar("All fields are required.", { variant: "error", autoHideDuration: '3s' });
    }

  }

  return (
    <Grid container className="page-container">
      <Grid item md={4} sm={6} xs={11} className="page-block">
        <span className="page-heading"> Login </span>
        <form className="mb-4" onSubmit={submitForm}>
          <TextField variant="standard" margin="normal" fullWidth
            label="Email Address" name="email" autoFocus 
            value={form.email.value} onChange={handleChange}
            />
          <TextField variant="standard" margin="normal"
            label="Password*" type={passwordVisibility ? "text" : "password"} fullWidth name="password"
            value={form.password.value} onChange={handleChange}
            InputProps={{
              endAdornment: (
                <InputAdornment position="end">
                  <IconButton edge="end" tabIndex="-1"
                    onClick={e => setPasswordVisibility(!passwordVisibility)} >
                    {passwordVisibility ? <VisibilityOff /> : <Visibility />}
                  </IconButton>
                </InputAdornment>
              ),
            }}
          />
          <Button type="submit" fullWidth disabled={processing} variant="contained" color="primary" > {processing ? "Processing..." : "Log In"} </Button>
          <p>
            <NavLink to={'/register'}>Create new account</NavLink>
          </p>

        </form>
      </Grid>
    </Grid>)
}

export default Login;

Finally, we had done the AuthGuard, Lets test the whole functionality.

Here We Go…

Start from register, if you have already register with same email address you can set this message.

Register form 3

If all goes well, you will see this welcome screen, with message Register Successfully.

Welcome Page 3

Try to log out. It will redirect you to login page.

The same way try to login and you will see how it is smooth.

Wrapping Up

So, this the the AuthGuard Concept where we can easily check user is Logged In or not. And implement as per our requirements.

Here is the whole project

https://github.com/RomikMakavana/react-authguard-demo

Thank you Guys…

Related Posts

There are no related posts yet. 😢