Protected routes on React with JWT

Viewed 837

I'm trying to create Protected Routes for my React project, on Back-End I have NodeJS. I look at quite a few examples and they use localStorage or "fakeAuth" so I don't really get proper info I need, the main problem I encountered is that when I log in how should I save that information that I'm logged in ATM so my Front-End part could see that.

In my PrivateRoute component I need to have boolean that describes my stats(Logged In or not), but I don't really get it how should I "extract" that information from my Back-End my PrivateRoute component: (Boolean should be in place of {{??????????????}} )

import React from "react";
import { Route, Redirect } from  "react-router-dom";
import {authentication} from "./login.component"
import axios from 'axios';
export const PrivateRoute = ({ component: Component, ...rest}) => (
    <Route 
        {...rest}
        render={props => 
            {{??????????????}} ? (
                <Component {...props} />
            ) : (
                <Redirect
                    to={{
                        pathname: "/login",
                        state: { from: props.location }
                    }}
                />  
            )
        }
    />
);

Here is my Log in component:

import React, { useState } from 'react';
import axios from 'axios';
const Login = () => {
    const [username, setUsername] = useState('');
    const [password, setPassword] = useState('');
    const onChangeUsername = (e) => {
    setUsername(e.target.value);
    };
    const onChangePassword= (e) => {
     setPassword(e.target.value);
    };
    const onSubmit = async (e) => {
        const user = {
            username: username,
            password: password
            };
        e.preventDefault();
        await axios.post('http://localhost:5000/users/login', user);
    };
    return (
        <div className="Login">
           <h2>Traffic scan admin panel</h2>
           <div className="LoginInfo-Logins">
            <form>
                <input type="text"
                required
                className=""
                value={username}
                onChange={onChangeUsername}
                placeholder="Username*" 
                />
                <input type="text"
                required
                className=""
                value={password}
                onChange={onChangePassword}
                placeholder="Password*" 
                /> 
            </form>
           </div>
           <button onClick={onSubmit}>SIGN IN</button>
        </div>
    )
}
export default Login;

And here is my Back-End part:

require('dotenv').config();

const router = require('express').Router();
const Users = require('../models/user.model');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');

router.route('/').get(authenticateToken, async (req, res) => {
    const user = await Users.findOne({username: req.body.username});
    if(!user) throw Error('User do not exist')
    res.json(user)
});

router.route('/add').post(async (req, res) => {
    try {
        const hashedPassword = await bcrypt.hash(req.body.password, 10); // 10 is const salt = await bcrypt.genSalt/*10 is default*/);
        const email = req.body.email;
        const username = req.body.username;
        const password = hashedPassword;
        const IP = req.connection.remoteAddress;
        const newUser = new Users({
            email,
            username,
            password,
            IP
        });
        newUser.save()
            .then(() => res.json('User added!'))
            .catch(err => res.status(400).json('Error: ' + err));
    } catch {
        res.status(500).send()
    }
});     
router.route('/login').post(async (req, res) => {
    const user = await Users.findOne({username: req.body.username});
    if(!user) throw Error('Password or Username')
    if(await bcrypt.compare(req.body.password, user.password)) {
        const accessToken = jwt.sign(user.toJSON(), process.env.ACCESS_TOKEN_SECRET, {expiresIn: 604800});
        res.json({ accessToken: accessToken });
    } else throw Error('Password or Username is incorrect');
    
});
function authenticateToken (req, res, next) {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1];
    if(token == null) return res.sendStatus(401);
    
    jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user)=>{
        if(err) return res.send(403);
        req.user = user;
        next();
    })
}
module.exports = router;

Maybe someone can help me out, any suggestions, tutorials, examples on how to do it would help.

1 Answers

Most of the tutorials use localStorage or cookie because you want to retain the state of the login even after the user has refreshed the page so they don't have to re-login every time. But to answer your question you can do this:

import React, { useState } from "react";
import { Route, Redirect, BrowserRouter } from "react-router-dom";
import "./style.css";
import axios from "axios";

function yourLoginPostApi() {
  return new Promise(r => setTimeout(() => r(true), 1000));
}

function Login({ onSuccess }) {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  let [loading, setLoading] = useState(false);
  const onChangeUsername = e => {
    setUsername(e.target.value);
  };
  const onChangePassword = e => {
    setPassword(e.target.value);
  };
  const onSubmit = async e => {
    const user = {
      username: username,
      password: password
    };
    e.preventDefault();
    setLoading(true);
    // Uncomment this line and remove the next one since it's a fake api call
    // let response = await axios.post("http://localhost:5000/users/login", user);
    let response = await yourLoginPostApi();
    setLoading(false);
    onSuccess(response);
  };
  return (
    <div className="Login">
      <h2>Traffic scan admin panel</h2>
      <div className="LoginInfo-Logins">
        <form>
          <input
            type="text"
            required
            className=""
            value={username}
            onChange={onChangeUsername}
            placeholder="Username*"
          />
          <input
            type="text"
            required
            className=""
            value={password}
            onChange={onChangePassword}
            placeholder="Password*"
          />
        </form>
      </div>
      <button onClick={onSubmit}>SIGN IN</button>
      <div>{loading && "Please wait..."}</div>
    </div>
  );
}

function PrivateRoute({ children }) {
  let [loggedIn, setLoggedIn] = useState(false);
  return loggedIn ? (
    <div>
      {children}
      <button onClick={() => setLoggedIn(false)}>log out</button>
    </div>
  ) : (
    <Login onSuccess={() => setLoggedIn(true)} />
  );
}

export default function App() {
  return (
    <BrowserRouter>
      <PrivateRoute>
        <div>Stuff only a logged in user should see</div>
      </PrivateRoute>
    </BrowserRouter>
  );
}

You can see the demo on stackblitz

Related