skip to Main Content

I have a react applicaton running on localhost:3000 and I have a backend Server running on localhost:4000,
If I try to navigate or access a protected route when I dont login, it should not allow me to see that page.

And When I login my backend is giving me a token in cookie which is httpOnly:true, known how do I do the auth flow properly an in secure way

Backend Api:

exports.login = async (req, resp) => {
    try {
        // fetch data
        const { email, password } = req.body

        // validation for fields required
        if (!email || !password) {
            return resp.status(200).json({
                status: "Failed",
                msg: "All fields are required in login !",
            })
        }

        // check if email exists in database
        // We also populate additionDetails
        const user = await User.findOne({ email: email })
            .populate("additionalDetails")
            .exec()

        console.log("Data fetched about User from Db:", user)
        // If user not found with provided email

        if (!user) {
            return resp.status(200).json({
                status: "Failed",
                msg: "User with given email dont exists. try again !"
            })
        }

        // user exists check password with hashed password

        const checkHashedPassword = await bcrypt.compare(password, user.password)


        // password is correct generate Token
        if (checkHashedPassword) {

            // In token we send user id, email and accountType

            const payload = {
                id: user._id,
                email: user.email,
                accountType: user.accountType,
            }

            const tokenOptions = {
                expiresIn: "24h"
            }

            //Token Expires in 2 hours
            const token = jwtToken.sign(payload, process.env.JWT_SECRET, tokenOptions)

            // we update the object
            // send token and password 
            user.password = undefined;
            user.token = token;
            console.log("User Modified", user)

            // generate cookie

            const cookieOptions = {
                // expires in 3days
                //expiresIn:"3hr, 3days" 

                expires: new Date(Date.now() + 3 * 24 * 60 * 1000),
                httpOnly: true,
            }

            resp.cookie("token", token, cookieOptions).
                // Req body
                status(200).json({
                    success: "Success",
                    token,
                    user,
                    message: 'User Logged in successfully !',
                })

        }
        else {
            return resp.status(200).json({
                status: "Failed",
                msg: "Password dont match ! Try Again !"
            })
        }

    } catch (error) {
        console.log(error);
        return resp.status(200).json({
            status: "Failed",
            message: 'Login Failure, please try again',
            errormsg: error
        });
    }
}

FrontEnd :

import { Routes, Route } from "react-router-dom";
import "./App.css";
import Navbar from "./components/core/Navbar";
import { HomePage } from "./pages/HomePage";
import CoursePage from "./pages/CoursePage";
import AboutUsPage from "./pages/AboutUsPage";
import LoginPage from "./pages/LoginPage";
import SignUpPage from "./pages/SignUpPage";
import ScrollToTop from "./ScrollToTop";
import ContactPage from "./pages/ContactPage";
import { Toaster } from "react-hot-toast";
import ProfilePage from "./pages/ProfilePage";
import ProtectedRoute from "./components/protectedRoute/ProtectedRoute";
import Test from "./pages/Test";
import {useSelector} from "react-redux"


function App() {

  const toastconfiguration={
    position:'top-center',
  }

  return (
    <div className="tw-font-inter tw-bg-richblack-900 tw-h-full  tw-relative">
        <Toaster position="top-center" toastOptions={toastconfiguration} />
        <Navbar />
        <ScrollToTop />

        <Routes>
          <Route path='/' element={<HomePage />} />
          <Route path='/courses' element={<CoursePage />} />
          <Route path='/about' element={<AboutUsPage />} />
          <Route path='/login' element={<LoginPage />} />
          <Route path='/signup' element={<SignUpPage />} />
          <Route path='/contact' element={<ContactPage />} />
            
          <Route element={<ProtectedRoute />}>
            <Route element={<ProfilePage />} path="/profile" />
            <Route element={<Test />} path="/test" />
          </Route>
        </Routes>
    </div >
  );
}

export default App;
 

import React from 'react'
import { Outlet,Navigate } from 'react-router-dom'

function ProtectedRoute() {
    //* Here add the logic of access the global auth token from redux 
    let auth={'token':false}  // This I have saved globally using redux but for now I want to known the solution
  return (
        auth.token?<Outlet/>:<Navigate to='/login'/>
    )
}

export default ProtectedRoute

I didnt find any article to properly understand it, I can send the token in resp and save it browser localStorage. But I dont known what would be the correct way. PLEASE HELP

2

Answers


  1. JWTs often have identifiable information in them, such as a user’s uuid as the most basic example. In general, it’s a fairly secure way to be able to identify your user, without giving up what the uuid is, and making it extremely difficult to generate a a uuid that decrypts to an actual user in your database.

    Some things that you might want to consider is embedding data that can’t change within the user’s "session" and validating that on every request. It’s not uncommon to include an expiration as well.

    An example of authentication might look like this: ( untested 🙂 )

    async function authenticateUserFromPassword(req, res) {
      try {
        const { email, password } = req.body;
    
        if (!email || !password) {
          return res.status(401).json({ err: "email and password are required" });
        }
    
        const user = await User.findOne({ email })
          .populate("additionalDetails")
          .exec();
        const validPassword =
          user && (await bcrypt.compare(password, user.password));
    
        if (!user || !validPassword) {
          return res.status(401).json({ err: "invalid login" });
        }
    
        const payload = {
          id: user.id,
          accountType: user.accountType,
          iss: "https://api.example.com",
          aud: ["https://example.com"],
          purpose: "access_token",
        };
    
        const token = jwtToken.sign(payload, process.env.JWT_SECRET, {
          expiresIn: "24h",
        });
    
        res.json({ token });
      } catch (error) {
        return res.status(500).send();
      }
    }
    
    
    async function validateAdminTokenMiddleWare (req, res, next) {
      try {
        const token = req.headers.authorization.split(" ")[1];
        const payload = jwtToken.verify(token, process.env.JWT_SECRET);
        if (payload.purpose !== "access_token") {
          return res.status(401).json({ err: "invalid token" });
        }
        
        if (payload.accountType !== "admin") {
          return res.status(401).json({ err: "invalid token" });
        }
    
        if (payload.iss !== "https://api.example.com") {
          return res.status(401).json({ err: "invalid token" });
        }
    
        if (!payload.aud.includes("https://example.com")) {
          return res.status(401).json({ err: "invalid token" });
        }
    
    
        const user = await User.findById(payload.id)
          .populate("additionalDetails")
          .exec();
        req.user = user;
        next();
      } catch (err) {
        return res.status(500).send();
      }
    }
    
    async function validateUserTokenMiddleWare (req, res, next) {
      try {
        const token = req.headers.authorization.split(" ")[1];
        const payload = jwtToken.verify(token, process.env.JWT_SECRET);
        if (payload.purpose !== "access_token") {
          return res.status(401).json({ err: "invalid token" });
        }
        
        if (payload.accountType !== "user") {
          return res.status(401).json({ err: "invalid token" });
        }
    
        if (payload.iss !== "https://api.example.com") {
          return res.status(401).json({ err: "invalid token" });
        }
    
        if (!payload.aud.includes("https://example.com")) {
          return res.status(401).json({ err: "invalid token" });
        }
    
    
        const user = await User.findById(payload.id)
          .populate("additionalDetails")
          .exec();
        req.user = user;
        next();
      } catch (error) {
        return res.status(500)send();
      }
    }
    

    We’re using different middleware to handle different user types accordingly.

    On the front-end, you’ll want to save the token to local storage or equivalent and attach the token to the Authorization header on all of your requests that require auth.

    Some things to note: It’s best practice to always check the iss, and uad claims. This helps to mitigate attack vectors where one resource server would obtain a genuine access token intended for it, and then use it to gain access to resources on a different resource server, which would not normally be available to the original server.

    I removed the cookie, you don’t need it. When you login, the server just responds with 200, and the token. The front-end should manage it from there.

    You don’t need to update a user object with the token, in fact it’s best not to store those tokens in the DB.

    Lastly, I would have a wrapper around fetch. whenever a 401 is encountered, remove the old token, and redirect to the login screen unless of course, you’re already on the login screen.

    Login or Signup to reply.
  2. for better caching

    1. You can use redis for store token.
    2. And in middleware add a condition to check wheather the token exist ( like as u need, multi login or prevent multi login,etc)
    3. validating via redis will strenghten your Auth module
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search