Skip to main content
Node.js APIs Tutorial
CHAPTER 12 Beginner

Authentication and JWT Tokens

Updated: May 14, 2026
30 min read

# CHAPTER 12

Authentication and JWT Tokens

1. Introduction

REST APIs are strictly "stateless." This means the server has no memory of previous requests. If you log in at 1:00 PM, and request your profile data at 1:01 PM, the API has already forgotten who you are! Traditional websites use "Cookies" and "Sessions" to remember users, but mobile apps (iOS/Android) cannot process standard browser cookies easily. To solve this, modern APIs use JSON Web Tokens (JWT). In this chapter, we will build a secure authentication system from scratch.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Explain why APIs use Tokens instead of Sessions.
  • Hash user passwords using bcrypt.
  • Generate a JWT upon successful login.
  • Create an Auth Middleware to protect private routes.

3. Beginner-Friendly Explanation

Imagine an Amusement Park. In a traditional website (Sessions), the security guard remembers your face. Every time you go to a ride, he looks at you and says, "Oh, I remember you, go ahead." In an API (Stateless), the security guard has amnesia. He forgets you instantly. To fix this, when you pay at the front gate, the park gives you a Wristband (JWT Token). Now, every time you want to ride a rollercoaster, you don't need the guard to remember your face; you just hold up your wristband. The wristband proves you paid.

4. Step 1: Registration and Password Hashing

NEVER save plain-text passwords in a database. If hacked, user data is compromised. We use bcrypt to encrypt the password before saving.

Install Dependencies:

bash
1
npm install bcrypt jsonwebtoken

controllers/authController.js (Register):

javascript
1234567891011121314151617181920212223
const User = require('../models/User'); // Mongoose Model
const bcrypt = require('bcrypt');

const register = async (req, res) => {
    try {
        const { username, password } = req.body;

        // 1. Generate a "salt" (random characters) and hash the password
        const salt = await bcrypt.genSalt(10);
        const hashedPassword = await bcrypt.hash(password, salt);

        // 2. Save the user with the HASHED password, not the real one!
        const newUser = new User({
            username: username,
            password: hashedPassword
        });

        await newUser.save();
        res.status(201).json({ message: "User registered successfully!" });
    } catch (err) {
        res.status(500).json({ error: err.message });
    }
};

5. Step 2: Login and Generating the JWT

When a user logs in, we compare their typed password to the hashed database password. If it matches, we generate a JWT string and send it to them.

controllers/authController.js (Login):

javascript
1234567891011121314151617181920212223
const jwt = require('jsonwebtoken');

const login = async (req, res) => {
    try {
        const { username, password } = req.body;
        const user = await User.findOne({ username });

        if (!user) return res.status(400).json({ error: "User not found" });

        // 1. Compare the typed password to the database hash
        const validPassword = await bcrypt.compare(password, user.password);
        if (!validPassword) return res.status(400).json({ error: "Invalid password" });

        // 2. SUCCESS! Generate the Wristband (JWT)
        // We embed the user's ID inside the token, and sign it with a secret key
        const token = jwt.sign({ id: user._id }, "MY_SUPER_SECRET_KEY", { expiresIn: "1h" });

        // 3. Send the token to the client
        res.status(200).json({ status: "success", token: token });
    } catch (err) {
        res.status(500).json({ error: err.message });
    }
};

6. Step 3: Protecting Routes (Auth Middleware)

The client now has the Token. Whenever they request private data (like /api/profile), they must attach this token to the Authorization HTTP Header. We write a Middleware to check this header.

middleware/auth.js:

javascript
123456789101112131415161718192021222324252627
const jwt = require('jsonwebtoken');

const verifyToken = (req, res, next) => {
    // 1. Look for the token in the Headers
    const token = req.header('Authorization');
    
    // If no token exists, block them!
    if (!token) return res.status(401).json({ error: "Access Denied. No token provided." });

    try {
        // 2. Verify the token using our secret key
        // Note: The client sends it as "Bearer eyJhbGciOi..." so we split the string to get just the token
        const actualToken = token.split(" ")[1]; 
        
        const verifiedData = jwt.verify(actualToken, "MY_SUPER_SECRET_KEY");
        
        // 3. Attach the decrypted user ID to the request object!
        req.user = verifiedData; 
        
        // 4. Let them onto the dance floor
        next(); 
    } catch (err) {
        res.status(400).json({ error: "Invalid Token" });
    }
};

module.exports = verifyToken;

7. Step 4: Applying the Middleware

Now, protect any route simply by adding verifyToken into the router chain.
javascript
123456789101112
const express = require('express');
const router = express.Router();
const verifyToken = require('../middleware/auth');

// This route is public
router.post('/login', authController.login);

// This route requires the Wristband!
router.get('/profile', verifyToken, (req, res) => {
    // Because the middleware attached req.user, we know exactly who is logged in!
    res.json({ message: "Welcome to your profile", userId: req.user.id });
});

8. Backend Workflow: Testing JWT in Postman

  1. 1. Make a POST request to /login. Copy the long "token" string from the JSON response.
  1. 2. Open a new GET request for /profile.
  1. 3. Go to the Authorization tab. Select Bearer Token.
  1. 4. Paste your token into the box and click Send. You will gain access!

9. Best Practices

  • Hide the Secret Key: In the code above, we hardcoded "MYSUPERSECRETKEY". In the real world, if a hacker finds your secret key, they can mint infinite valid tokens and take over your entire platform. This key MUST be hidden inside a .env file (e.g., process.env.JWTSECRET).

10. Common Mistakes

  • The Bearer Prefix: The industry standard is to send the token as Bearer <token>. Beginners often forget to split the string in the middleware, and they accidentally try to verify the word "Bearer" along with the token, causing a JsonWebTokenError.

11. Exercises

  1. 1. Explain the architectural reason why REST APIs use JSON Web Tokens (stateless) instead of traditional Session Cookies (stateful).

12. Coding Challenges

  • Challenge: Modify the JWT generation code so that the token expires in exactly 15m (15 minutes), and includes the user's role (e.g., 'admin') inside the token payload along with the ID.

13. MCQs with Answers

Question 1

What is the primary purpose of the bcrypt library in Node.js?

Question 2

When a mobile application wants to access a protected API route, where does it standardly place the JWT token within the HTTP request?

14. Interview Questions

  • Q: Walk me through the complete authentication lifecycle of a Node.js API, starting from the moment a user submits their login credentials, to the moment they successfully retrieve their private profile data.
  • Q: What is the payload of a JWT? Is it encrypted? (Hint: It is base64 encoded, not encrypted. Anyone can read it, but no one can alter it without the secret key).

15. FAQs

Q: What do I do when a token expires? Does the user have to log in again? A: In simple apps, yes. In advanced apps, developers use a "Refresh Token" system. When the short-lived 15-minute Access Token expires, the app secretly sends a long-lived Refresh Token to the server to silently get a new Access Token without bothering the user.

16. Summary

In Chapter 12, we secured our API. By integrating bcrypt, we ensured that user passwords are cryptographically hashed before touching our database. By issuing JSON Web Tokens (JWT), we adhered to the stateless rules of REST architecture, allowing mobile apps and SPAs to seamlessly authenticate. Finally, by writing custom Auth Middleware, we built a robust security wall to protect our private endpoints.

17. Next Chapter Recommendation

Our users can log in, but what if they register with an invalid email address? Our database will crash. Proceed to Chapter 13: API Validation and Error Handling.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·