← Back

JWT for authorization

Dec 29, 2023

-

Nidhish

JWT auth

Use cases of JWT and implementation of auth.

Introduction

We will be going through some topics on how authentication works using JWT. For beginners to understand the content easily, we will be using some raw-dog methods like localStorage to store and manage JWT tokens in the browser. Please note that we will be using localStorage for only learning purpose and in a real world application, httpOnly is used to store tokens in cookies. Now what is cookies and httpOnly? That is a talk for another time. But you will understand at the end of the post on why we use these methods and how it all actually works.

Table of Contents

  1. JWT use cases
  2. User Sign-Up
  3. User Sign-In
  4. JWT Token Generation
  5. Storing Token in localStorage
  6. Persistent Authentication
  7. Conclusion

1. JWT use cases

You may have heard of people using JWT (jsonwebtoken) for managing authentications in applications. You might think that it is used to secure your user data and passwords right? Wrong, JWT only handles the process of securely transmitting information between the client & server. The rest of the work is done by the server which you have written such as verifying if user is already present, if the password is valid, encrypting/hashing of passwords, saving user data to database, etc. Then you might think why use JWTs at all right?

Here are the reasons:

2. User Sign-Up

For starters, refer the below image to understand how sign-up works:

sign-up

Create a MongoDB model for the user and set up a route for user registration.

// userModel.js
const mongoose = require("mongoose");

const userSchema = new mongoose.Schema({
    username: { type: String, unique: true, required: true },
    password: { type: String, required: true },
});

const User = mongoose.model("User", userSchema);

module.exports = User;
// signupRoute.js
const express = require("express");
const bcrypt = require("bcrypt");
const User = require("./userModel");

const router = express.Router();

router.post("/signup", async (req, res) => {
    try {
        const { username, password } = req.body;

        // Hash the password
        const hashedPassword = await bcrypt.hash(password, 10);

        // Create a new user
        const newUser = new User({
            username,
            password: hashedPassword,
        });

        // Save the user to the database
        await newUser.save();

        res.status(201).json({ message: "User registered successfully" });
    } catch (error) {
        res.status(500).json({ error: "Internal Server Error" });
    }
});

module.exports = router;

3. User Sign-In

For the first sign-in, JWT is used to sign and generate a token for the access the user is going to use later. When the user is singed in, the token is then sent to frontend as given below:

sign-in

Implement the user sign-in route.

// signinRoute.js
const express = require("express");
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const User = require("./userModel");

const router = express.Router();

router.post("/signin", async (req, res) => {
    try {
        const { username, password } = req.body;

        // Find the user in the database
        const user = await User.findOne({ username });

        // Check if the user exists
        if (!user) {
            return res
                .status(401)
                .json({ error: "Invalid username or password" });
        }

        // Check if the password is correct
        const passwordMatch = await bcrypt.compare(password, user.password);

        if (!passwordMatch) {
            return res
                .status(401)
                .json({ error: "Invalid username or password" });
        }

        // Sign a JWT token
        const token = jwt.sign(
            { userId: user._id, username: user.username },
            "your-secret-key",
            { expiresIn: "1h" }
        );

        res.json({ token });
    } catch (error) {
        res.status(500).json({ error: "Internal Server Error" });
    }
});

module.exports = router;

4. JWT Token Generation

The next process is explained in the diagram. You can follow the steps to understand how the auth token is stored in client and reused by server for other requests.

jwt

Create a route middleware to verify the JWT token before accessing protected routes.

// authMiddleware.js
const jwt = require("jsonwebtoken");

const authMiddleware = (req, res, next) => {
    const token = req.headers.authorization?.split(" ")[1];

    if (!token) {
        return res.status(401).json({ error: "Unauthorized" });
    }

    try {
        const decodedToken = jwt.verify(token, "your-secret-key");
        req.userData = decodedToken;
        next();
    } catch (error) {
        res.status(401).json({ error: "Unauthorized" });
    }
};

module.exports = authMiddleware;

5. Storing Token in localStorage

Modify your sign-in route to include the JWT token in the response.

// signinRoute.js
// ... (previous code)

router.post("/signin", async (req, res) => {
    try {
        // ... (previous code)

        // Sign a JWT token
        const token = jwt.sign(
            { userId: user._id, username: user.username },
            "your-secret-key",
            { expiresIn: "1h" }
        );

        res.json({ token });
    } catch (error) {
        res.status(500).json({ error: "Internal Server Error" });
    }
});

// ... (remaining code)

6. Persistent Authentication

Implement a route that checks for a valid token stored in localStorage on page load.

// persistentAuthRoute.js
const express = require("express");
const authMiddleware = require("./authMiddleware");

const router = express.Router();

router.get("/dashboard", authMiddleware, (req, res) => {
    // If the middleware passes, the user is authenticated
    res.json({ message: "You are authenticated!", userData: req.userData });
});

module.exports = router;

7. Conclusion

This is just the basics implemented using JWT authentication with Node.js, Express, MongoDB, and localStorage. This provides a foundation for building secure and scalable web applications. Remember to handle secret keys and sensitive information securely in a production environment. Only use localStorage for learning purpose with JWTs.

Happy coding!