Creating private routes and handling session in react.js

August 12, 2020

drawing
  • In the previous tutorial, we covered how to create basic login and registration forms using react.js
  • In today’s tutorial, we are going to cover how to protect internal app pages from unauthorised access using client-side session token.
  • We can set-up backend using this excellent and simple article on dev.to: Backend Setup and make changes as shown in the final API route as shown in this file: Github
  • Now if you observe the response for login/registration APIs in the backend APIs article above, you can find that there is an access-token returned in the response:
 res.status(200).json({
                        token
                    });
  • We need to save this token on client-side for managing the user session. So after receiving the response from registration API we need to make the change as shown below on line 6 in the `RegistrationForm.js` file:-

if(response.status === 200){
setState(prevState => ({
...prevState,
'successMessage' : 'Registration successful. Redirecting to home page..'
}))
localStorage.setItem(ACCESS_TOKEN_NAME,response.data.token);
redirectToHome();
props.showError(null)
}
view raw responsestore.js hosted with ❤ by GitHub

  • localStorage.setItem is used to store the token received from backend API to browser’s local storage.
  • Similar change needs to be made in Login API call in Login.js file after receiving the response from server as shown on line 34 here: Login.js
  • Next create a utils folder and file called PrivateRoute.js as shown below:

Private Route

  • The code for Private Route is pretty straightforward:
    import React from 'react';
    import { Redirect, Route } from "react-router-dom";
    import { ACCESS_TOKEN_NAME } from '../constants/apiContants';
    function PrivateRoute({ children, ...rest }) {
    return (
    <Route
    {...rest}
    render={({ location }) =>
    localStorage.getItem(ACCESS_TOKEN_NAME) ? (
    children
    ) : (
    <Redirect
    to={{
    pathname: "/login",
    state: { from: location }
    }}
    />
    )
    }
    />
    );
    }
    export default PrivateRoute;
    view raw privateroute.js hosted with ❤ by GitHub
  • We import the Route component from react-router-dom on line 2. On line 9 there is a condition to check whether access-token is present in the browser’s local storage. If a token is present then access to requested Component/Route is granted else the user is redirected to the login page.
  • Next, we make the Home Component in our application private by making below changes in the App.js file:
    ...
    import PrivateRoute from './utils/PrivateRoute';
    ...
    <Switch>
    .....
    <PrivateRoute path="/home">
    <Home/>
    </PrivateRoute>
    </Switch>
    view raw privateroute2.js hosted with ❤ by GitHub
  • By using PrivateRoute on line 7 above we make sure that the homepage is only accessible when a token is present on client-side.
  • After this, we need to make sure that the access token has not expired in future API calls within the private routes. For example, let’s say any API written to fetch data within the homepage should send token in headers to check it’s validity. If the token is invalid then the user should be redirected to the login page.
  • A sample API could be written in Home.js file as shown below:-
    function Home(props) {
    useEffect(() => {
    axios.get(API_BASE_URL+'/user/me', { headers: { 'token': localStorage.getItem(ACCESS_TOKEN_NAME) }})
    .then(function (response) {
    if(response.status !== 200){
    redirectToLogin()
    }
    })
    .catch(function (error) {
    redirectToLogin()
    });
    })
    function redirectToLogin() {
    props.history.push('/login');
    }
    ...
    }
    view raw fetchCall.js hosted with ❤ by GitHub
  • Here we send the ‘token’ in headers section for checking its validity. The auth middleware written in out backend APIs takes care of checking token validity:-
    const jwt = require("jsonwebtoken");
    const User = require('../models/User');
    module.exports = async function(req, res, next) {
    const token = req.header("token");
    if (!token) return res.status(401).json({ message: "Auth Error" });
    try {
    const decoded = jwt.verify(token, "randomString");
    req.user = decoded.user;
    const user = await User.findById(decoded.user.id);
    next();
    } catch (e) {
    console.error(e);
    res.status(500).send({ message: "Invalid Token" });
    }
    };
    view raw auth.js hosted with ❤ by GitHub
  • The last thing to do is to add a logout button in our application to the end user session and redirect the user back to the login page. We can add a logout button in our app as shown below:-
    function Header(props) {
    ...
    function renderLogout() {
    if(props.location.pathname === '/home'){
    return(
    <div className="ml-auto">
    <button className="btn btn-danger" onClick={() => handleLogout()}>Logout</button>
    </div>
    )
    }
    }
    function handleLogout() {
    localStorage.removeItem(ACCESS_TOKEN_NAME)
    props.history.push('/login')
    }
    return(
    <nav className="navbar navbar-dark bg-primary">
    <div className="row col-12 d-flex justify-content-center text-white">
    <span className="h3">{props.title || title}</span>
    {renderLogout()}
    </div>
    </nav>
    )
    }
    view raw headerupdated.js hosted with ❤ by GitHub
  • We have defined a renderLogout method on line 3 which displays a logout button in the header section if the user is on the home page. The handleLogout function on line 12 takes care of destroying session token on the client-side and redirecting user to the login page.
  • This completes a short tutorial on creating private routes and handling authentication on client-side using react.js. The complete source code for this section can be found here: Github.
    If you would like to see how to use this complete setup on a sample app then you can check out the quiz app which I created in this repo: Github
  • If you have any doubts in this tutorial then reach out to me on LinkedIn or Twitter

Saurabh Mhatre

Blog by Saurabh Mhatre
Follow me on Twitter