DEV Community

Vinicius Blazius Goulart
Vinicius Blazius Goulart

Posted on • Updated on • Originally published at dev.to

How to protect a route with Token in React using Context API and React Cookies

What will be done?

We’ll learn how to protect a route by JWT authentication in react-router-dom v6. The routes will only be accessible when users have the token saved in cookies(or local storage).

Used technologies

  • React ^18.2.0;
  • JWT;
  • Axios ^0.27.2;
  • react-router-dom ^6.3.0;
  • react-cookie ^4.1.1;

Starting

Let's create a React App

create-react-app protect-route-with-jwt
Enter fullscreen mode Exit fullscreen mode

This command will create a react project structure with some necessary files. Remove some files and create some folders that will be used in the project. The final structure will be like this:
Image description

Now, we are gonna install some project dependencies, open the terminal inside of project folder, and run the following commands:

yarn init
yarn add axios react-router-dom react-cookie
Enter fullscreen mode Exit fullscreen mode

Defining API

Create a file in src > services > api.js where we'll define the baseURL to make requests in our application:

import axios from 'axios';

const api = axios.create({
    baseURL: "http://localhost:8000" //your api URL
});

export default api;
Enter fullscreen mode Exit fullscreen mode

src > services > api.js

We are defining our api variable from axios library, and exporting it.

Creating Hooks

Navigate to src > hooks creating two folders, the auth and theprotect Routes. Inside them and the hooks folder create a index.js file.
Image description

auth

import { createContext, useContext, useMemo } from 'react';
import { useCookies } from 'react-cookie';
import { useNavigate } from 'react-router-dom';
import api from '../../services/api';

const UserContext = createContext();

export const UserProvider = ({ children }) => {
    const navigate = useNavigate();
    const [cookies, setCookies, removeCookie] = useCookies();

    const login = async ({ email, password }) => {
        const res = await api.post('/auth', {
            email: email,
            password: password
        });

        setCookies('token', res.data.token); // your token
        setCookies('name', res.data.name); // optional data

        navigate('/home');
    };

    const logout = () => {
        ['token', 'name'].forEach(obj => removeCookie(obj)); // remove data save in cookies
        navigate('/login');
    };

    const value = useMemo(
        () => ({
            cookies,
            login,
            logout
        }),
        [cookies]
    );

    return (
        <UserContext.Provider value={value}>
            {children}
        </UserContext.Provider>
    )
};

export const useAuth = () => {
    return useContext(UserContext)
};
Enter fullscreen mode Exit fullscreen mode

src > hooks > auth > index.js

Basically, here we are creating the user Context that have login and logout functions, and exporting it to can be used through the component tree without having to pass props down manually at every level. If you dont understand see the contextAPI documentation.

protectRoutes

import { Outlet, Navigate } from 'react-router-dom';
import { useAuth } from '../auth';

export const ProtectRoutes = () => {
    const { cookies } = useAuth();

    return cookies.token ? <Outlet/> : <Navigate to='/login' exact />
};
Enter fullscreen mode Exit fullscreen mode

src > hooks > protectRoutes > index.js

Here we are creating a Route Protection using our useAuth that we define previously, if exists token in cookies follow the application, else redirect to login page.

hooks

Create the provider index of hooks adding all providers that we'll use in the application. In this case, only the auth hook that will be used in a global context of the application, so just import and add it.

import { UserProvider } from './auth';

const AppProvider = ({ children }) => (
    <>
        <UserProvider>{ children }</UserProvider>
    </>
);

export default AppProvider;
Enter fullscreen mode Exit fullscreen mode

src > hooks > index.js

Now, everything we put inside the AppProvider will be able to access the useAuth hook.

Creating Pages

Create two pages for this example, home and login page. They will be located in src > pages:
Image description
The example pages will be like this:

import React from 'react';

export default function Home() {
    return <div>Home Page</div>
}

Enter fullscreen mode Exit fullscreen mode

src > pages > Home > index.js

import React, { useState } from 'react';
import { useAuth } from "../../hooks/auth";

export default function Login() {
    const [cnpj, setCnpj] = useState('');
    const [password, setPassword] = useState('');
    const { login } = useAuth();

    const handleLogin = () => {
        login({ cnpj, password });
    }

    return (
        <div>
            <input onChange={e => setCnpj(e.target.value)} placeholder="Email"/>
            <input onChange={e => setPassword(e.target.value)} placeholder="Password"/>
            <button onClick={handleLogin} type="submit">Login</button>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

src > pages > Login > index.js

Creating Routes

Navigate to index.js of src folder, and add the following code:

import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import AppProvider from './hooks';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <BrowserRouter>
      <AppProvider>
        <App />
      </AppProvider>
    </BrowserRouter>
  </React.StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

src > index.js

Let's define some applications routes and, finally, protect them. Navigate to App.js and add the following code:

import React from 'react';
import { Route, Routes, Navigate } from 'react-router-dom';
import { ProtectRoutes } from './hooks/protectRoutes';
import Home from './pages/Home';
import Login from './pages/Login';

export default function App() {
  return (
    <Routes>
      <Route path='/' element={ <Navigate to='home' exact /> } />
      <Route path='/login' element={ <Login /> } />

      <Route element={ <ProtectRoutes /> }>
        <Route path='/home' element={ <Home /> } />
      </Route>
    </Routes>
  )
}
Enter fullscreen mode Exit fullscreen mode

src > App.js

Here, we define routes inside Routes component. To add our Protectd Route around the routes that we want protect, enough open a <Route> tag with our ProtectRoutes as the element. Inside the tag we'll add the routes, in this case we are protecting the home route, that is, the user will only be able to access it if they have a token in the cookies, which is what we defined in the protection of the route previously.

That is all, now all routes that you need protect with a user authentication, must be placed in Route that have <ProtectRoutes> element.

You can access this project in my github: https://github.com/ViniBGoulart/protect-route-with-jwt

Latest comments (8)

Collapse
 
steveghost profile image
steveghost

there is a way to separate the code by file, i.e. logout, login setCookie with login to help me please

Collapse
 
steveghost profile image
steveghost

I use your code but it generates me a bug on the login in the console.log it tells me that login is not a function can you please help me

Collapse
 
edmondtabaku profile image
Young Yeti

I am having a problem with this one. Whenever I log in it redirects me to my home page then after 1 second goes back to the login page

Collapse
 
vinibgoulart profile image
Vinicius Blazius Goulart • Edited

i'm update this project, see in github.com/ViniBGoulart/route-prot...

Collapse
 
ghiblin profile image
Alessandro Vitali • Edited

It’s a bad practice to store JWT Token in local storage because that expose your site to a lot of vulnerabilities and problems (think about what can happen if somebody can steal your token or what happen if you change the user permissions).
A much better way is to use session tokens and retrieve user capabilities from a dedicated API endpoint on application start up.

Collapse
 
vinibgoulart profile image
Vinicius Blazius Goulart • Edited

yes, good point, the idea of the project would be to store a token in the cookies, I used the jwt that comes directly from the backend just for a example

Collapse
 
femi_akinyemi profile image
Femi Akinyemi

I think the title should be How to protect a route with JWT Token in React using Context API and React Cookies

Collapse
 
vinibgoulart profile image
Vinicius Blazius Goulart

Yes... thnx, I didnt see this error