Development July 13, 2026

JWT Authentication in Node.js: A Complete Guide with Best Practices (2026)

Introduction

Authentication is one of the most critical aspects of modern web applications. Whether you're building a Learning Management System (LMS), an e-commerce platform, a social media application, or a SaaS product, verifying a user's identity securely is essential.

One of the most widely adopted authentication methods today is JSON Web Token (JWT) authentication. Unlike traditional session-based authentication, JWT enables applications to remain stateless, making it ideal for REST APIs and distributed systems.

In this guide, you'll learn:

  • What JWT is

  • How JWT authentication works

  • JWT structure

  • Implementing JWT in Node.js

  • Refresh Tokens

  • Access Tokens

  • Security best practices

  • Common mistakes developers make

  • Production-ready architecture


What is JWT?

JWT (JSON Web Token) is an open standard (RFC 7519) used for securely transmitting information between two parties as a JSON object.

A JWT contains digitally signed data, allowing the server to verify that the token hasn't been modified.

Example:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJ1c2VySWQiOiIxMjM0NTYiLCJyb2xlIjoiYWRtaW4ifQ
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Although it looks like random text, it's simply three Base64URL-encoded parts.


JWT Structure

A JWT consists of three sections separated by dots.

HEADER.PAYLOAD.SIGNATURE

1. Header

Contains metadata about the token.

Example:

{
  "alg": "HS256",
  "typ": "JWT"
}
  • alg → Signing algorithm

  • typ → Token type


2. Payload

Contains claims (user information).

Example:

{
  "userId": "65d93",
  "email": "john@example.com",
  "role": "student"
}

Common claims include:

  • userId

  • email

  • role

  • issued at

  • expiration time

Never store sensitive information like passwords in the payload because anyone can decode it.


3. Signature

Generated using:

HMACSHA256(
base64UrlEncode(header) +
"." +
base64UrlEncode(payload),
secretKey
)

The signature guarantees the token hasn't been altered.


How JWT Authentication Works

The authentication flow is straightforward:

  1. User enters email and password.

  2. Server verifies the credentials.

  3. Server generates a JWT.

  4. Token is sent to the client.

  5. Client stores the token securely.

  6. Client sends the token with every request.

  7. Server verifies the token before allowing access.

Flow Diagram:

User
 │
 │ Login
 ▼
Server
 │ Verify Credentials
 ▼
Generate JWT
 │
 ▼
Client
 │
 │ API Request
 │ Authorization: Bearer TOKEN
 ▼
Server
 │ Verify JWT
 ▼
Protected Resource

Installing Required Packages

npm install jsonwebtoken bcrypt dotenv

Optional packages:

npm install cookie-parser

Creating a JWT

const jwt = require("jsonwebtoken");

const token = jwt.sign(
  {
    id: user._id,
    role: user.role
  },
  process.env.JWT_SECRET,
  {
    expiresIn: "15m"
  }
);

Here:

  • Payload contains user information

  • Secret signs the token

  • Token expires in 15 minutes


Verifying a JWT

const jwt = require("jsonwebtoken");

try {
    const decoded = jwt.verify(
        token,
        process.env.JWT_SECRET
    );

    console.log(decoded);

} catch (err) {
    console.log("Invalid Token");
}

If verification fails, an error is thrown.


Authentication Middleware

A middleware protects private routes.

const jwt = require("jsonwebtoken");

module.exports = (req, res, next) => {

    const authHeader = req.headers.authorization;

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

    const token = authHeader.split(" ")[1];

    try {

        const decoded = jwt.verify(
            token,
            process.env.JWT_SECRET
        );

        req.user = decoded;

        next();

    } catch {

        return res.status(401).json({
            message: "Invalid Token"
        });

    }

};

Usage:

router.get(
    "/profile",
    authMiddleware,
    profileController
);

Access Token vs Refresh Token

Many beginners use only one JWT.

A production application should use two tokens.

Access Token Refresh Token
Short lifetime Long lifetime
Used for API requests Generates new access tokens
10–20 minutes 7–30 days

Workflow:

Login

↓

Access Token (15 min)

↓

Expires

↓

Refresh Token

↓

New Access Token

This approach improves security without forcing users to log in repeatedly.


Where Should JWT Be Stored?

Good

  • HttpOnly Cookies

  • Secure Cookies

Acceptable

  • Memory (React State)

Avoid

  • LocalStorage (for sensitive applications)

  • SessionStorage (less secure than HttpOnly cookies)

HttpOnly cookies prevent JavaScript from reading the token, reducing XSS risks.


JWT Authentication Flow with Refresh Tokens

Login
 │
 ▼
Generate Access Token
Generate Refresh Token
 │
 ▼
Store Refresh Token in Database
 │
 ▼
Return Tokens
 │
 ▼
API Request
 │
 ▼
Verify Access Token
 │
 ▼
If Expired
 │
 ▼
Verify Refresh Token
 │
 ▼
Generate New Access Token

Refresh Token Rotation

A secure implementation rotates refresh tokens.

Old flow:

Refresh Token
↓

Reuse Forever

Secure flow:

Refresh Token

↓

Generate New Refresh Token

↓

Delete Old Token

Benefits:

  • Prevents replay attacks

  • Limits damage if a token is stolen

  • Allows better session management


Token Expiration Strategy

Recommended values:

Token Duration
Access Token 15 minutes
Refresh Token 7 days
Remember Me 30 days

Password Hashing

Never store plain passwords.

Use bcrypt.

const bcrypt = require("bcrypt");

const hash = await bcrypt.hash(password, 12);

Login:

const isMatch =
await bcrypt.compare(
password,
user.password
);

Environment Variables

Never hardcode secrets.

JWT_SECRET=super_secret_key
ACCESS_TOKEN_SECRET=access_secret
REFRESH_TOKEN_SECRET=refresh_secret

Common JWT Errors

Token Expired

jwt expired

Solution:

Generate a new access token using the refresh token.


Invalid Signature

invalid signature

Cause:

Wrong secret key.


Malformed Token

jwt malformed

Cause:

Corrupted token.


Security Best Practices

Always:

  • Use HTTPS in production.

  • Store refresh tokens securely.

  • Hash refresh tokens before saving them in the database.

  • Rotate refresh tokens after each use.

  • Set short expiration times for access tokens.

  • Validate user roles and permissions.

  • Implement logout by revoking refresh tokens.

  • Rate-limit authentication endpoints.

  • Monitor and log suspicious authentication activity.

Never:

  • Store passwords in JWTs.

  • Expose secret keys.

  • Trust decoded payloads without verification.

  • Use extremely long-lived access tokens.

  • Embed sensitive personal information in token payloads.


Common Mistakes Beginners Make

  1. Using one JWT forever.

  2. Keeping access tokens valid for months.

  3. Saving secrets in Git repositories.

  4. Storing passwords in JWT payloads.

  5. Forgetting token expiration.

  6. Skipping role-based authorization.

  7. Not invalidating refresh tokens on logout.


Production Architecture

A scalable authentication setup might look like this:

Client
   │
   ▼
Express API
   │
JWT Middleware
   │
Controllers
   │
MongoDB
   │
Refresh Token Collection

Suggested project structure:

src/
│
├── controllers/
├── middleware/
│     └── auth.middleware.js
├── models/
│     └── Session.js
├── routes/
├── utils/
├── services/
├── config/
└── app.js

Conclusion

JWT authentication provides a powerful, scalable, and stateless solution for securing modern Node.js applications. When combined with short-lived access tokens, rotating refresh tokens, secure password hashing, and proper middleware, it forms the foundation of a robust authentication system.

For production systems, prioritize security over convenience. Use HTTPS, store refresh tokens securely (preferably hashed), implement token rotation, and keep access tokens short-lived. By following these practices, you can build authentication systems that are both user-friendly and resilient against common attack vectors.

Free Career Guidance

Start Your Learning Journey Today

Fill out the form below and our team will contact you with course details, fees, and career guidance.