Skip to content

Node.js JWT Authentication — Complete Guide to JSON Web Tokens

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Node.js JWT Authentication. We cover key concepts, practical examples, and best practices to help you master this topic.

Node.js JWT authentication uses signed JSON tokens to verify user identity across requests, enabling stateless authentication with configurable payload claims and expiration.

What You'll Learn

By the end of this tutorial, you'll implement JWT-based authentication, generate access and refresh tokens, verify tokens in middleware, handle token revocation, and choose signing algorithms.

Why JWT Matters

JWTs enable stateless authentication without server-side sessions. This simplifies scaling across multiple servers and Microservices while maintaining user context.

Real-World Use

An Express API issues short-lived access tokens (15 min) and long-lived refresh tokens (7 days). Mobile apps store refresh tokens securely and request new access tokens transparently.

JWT Path

flowchart LR
  A[Crypto] --> B[JWT]
  B --> C[OAuth]
  C --> D[Security]
  D --> E[Deployment]
  B --> F{You Are Here}
  style F fill:#f90,color:#fff

Basic Token Generation

Generate a JWT with a payload, secret, and expiration time.

const jwt = require("jsonwebtoken");
const SECRET = process.env.JWT_SECRET || "your-256-bit-secret";
const token = jwt.sign(
  { userId: 123, role: "admin" },
  SECRET,
  { expiresIn: "15m", issuer: "myapp" }
);
console.log("Access token:", token);
// Decode without verification (for debugging)
console.log("Decoded:", jwt.decode(token));

Token Verification Middleware

Verify JWTs in Express middleware and attach user data to the request.

const jwt = require("jsonwebtoken");
function authenticate(req, res, next) {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith("Bearer ")) {
    return res.status(401).json({ error: "Missing authorization header" });
  }
  const token = authHeader.split(" ")[1];
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET, {
      algorithms: ["HS256"],
      issuer: "myapp",
    });
    req.user = decoded;
    next();
  } catch (err) {
    if (err.name === "TokenExpiredError") {
      return res.status(401).json({ error: "Token expired", code: "TOKEN_EXPIRED" });
    }
    return res.status(403).json({ error: "Invalid token" });
  }
}

Refresh Token Pattern

Use refresh tokens to issue new access tokens without requiring login.

const jwt = require("jsonwebtoken");
const REFRESH_SECRET = process.env.REFRESH_SECRET || "refresh-secret";
function generateTokens(userId, role) {
  const accessToken = jwt.sign({ userId, role }, process.env.JWT_SECRET, { expiresIn: "15m" });
  const refreshToken = jwt.sign({ userId, type: "refresh" }, REFRESH_SECRET, { expiresIn: "7d" });
  return { accessToken, refreshToken };
}
function refreshAccessToken(refreshToken) {
  try {
    const decoded = jwt.verify(refreshToken, REFRESH_SECRET);
    if (decoded.type !== "refresh") throw new Error("Invalid token type");
    return generateTokens(decoded.userId, decoded.role);
  } catch {
    return null;
  }
}

Asymmetric Signing with RSA

Use RS256 with public/private key pairs for multi-service architectures.

const jwt = require("jsonwebtoken");
const fs = require("node:fs");
const privateKey = fs.readFileSync("private.pem", "utf8");
const publicKey = fs.readFileSync("public.pem", "utf8");
function signWithRSA(payload) {
  return jwt.sign(payload, privateKey, {
    algorithm: "RS256",
    expiresIn: "1h",
    issuer: "auth-service",
  });
}
function verifyWithRSA(token) {
  return jwt.verify(token, publicKey, {
    algorithms: ["RS256"],
    issuer: "auth-service",
  });
}
// Generate keys: openssl genrsa -out private.pem 2048 && openssl rsa -in private.pem -pubout -out public.pem

Token Blacklisting

Invalidate tokens before expiration using a blacklist.

const { createClient } = require("redis");
const redis = createClient();
async function blacklistToken(jti, expiresIn) {
  await redis.set(`blacklist:${jti}`, "true", { EX: expiresIn });
}
async function isBlacklisted(jti) {
  return (await redis.exists(`blacklist:${jti}`)) === 1;
}
function verifyWithBlacklist(token) {
  const decoded = jwt.decode(token);
  if (!decoded) throw new Error("Invalid token");
  return isBlacklisted(decoded.jti).then((blocked) => {
    if (blocked) throw new Error("Token revoked");
    return jwt.verify(token, process.env.JWT_SECRET);
  });
}

Common Mistakes

1. Storing Secrets in JWT Payload

JWT payload is base64-encoded, not encrypted. Never store passwords, credit cards, or secrets.

2. Not Setting Short Expiration

Long-lived tokens increase breach impact. Use 15-minute access tokens with refresh tokens.

3. Using None Algorithm

The "none" algorithm bypasses verification. Always specify allowed algorithms in jwt.verify.

4. Ignoring Token Leakage

JWTs in URLs are logged by proxies. Send tokens in Authorization headers only.

5. No Token Rotation

Using the same refresh token indefinitely increases risk. Rotate refresh tokens on each use.

Practice Questions

1. What are the three parts of a JWT?

Header (algorithm, type), Payload (claims), Signature (verification). Each is base64url-encoded and dot-separated.

2. What is the difference between HS256 and RS256?

HS256 uses a shared secret (symmetric). RS256 uses public/private key pair (asymmetric). RS256 allows separate signing and verification services.

3. How do you handle token expiration on the client?

Check token expiry before requests. Use refresh tokens to get new access tokens transparently.

4. What is a refresh token attack?

An attacker steals a refresh token and generates new access tokens. Mitigate with rotation and revocation.

5. Challenge: Implement a complete JWT auth system with access and refresh tokens.

const jwt = require("jsonwebtoken");
const users = [{ id: 1, email: "test@test.com", password: "hashed" }];
function login(email, password) {
  const user = users.find((u) => u.email === email);
  if (!user || password !== "hashed") throw new Error("Invalid credentials");
  return generateTokens(user.id, "user");
}
const tokens = login("test@test.com", "hashed");
console.log("Access:", tokens.accessToken);
console.log("Refresh:", tokens.refreshToken);

FAQ

Are JWTs encrypted?

No. JWTs are signed, not encrypted. Payload is base64-encoded. Anyone can decode without the key. Use JWE for encryption.

What is the jti claim?

JWT ID: a unique identifier for the token. Used for blacklisting individual tokens.

Can I use JWTs for sessions?

Yes, but JWTs cannot be revoked server-side without a blacklist. Sessions with server-side storage offer revocation.

How do I handle JWT secret rotation?

Support multiple keys with a kid header. Keep old key for verification until all tokens expire.

What is the best JWT library for Node.js?

jsonwebtoken is the most popular. jose is a newer alternative with modern API and no dependencies.

Mini Project: JWT Auth Middleware

Build a complete authentication middleware with refresh token support.

const jwt = require("jsonwebtoken");
class AuthService {
  constructor(secret, refreshSecret) {
    this.secret = secret;
    this.refreshSecret = refreshSecret;
    this.refreshTokens = new Map();
  }
  login(userId, role) {
    const access = jwt.sign({ userId, role }, this.secret, { expiresIn: "15m", jwtid: crypto.randomUUID() });
    const refresh = jwt.sign({ userId, role }, this.refreshSecret, { expiresIn: "7d" });
    this.refreshTokens.set(refresh, { userId, role });
    return { access, refresh };
  }
  refresh(refreshToken) {
    const stored = this.refreshTokens.get(refreshToken);
    if (!stored) throw new Error("Invalid refresh token");
    this.refreshTokens.delete(refreshToken);
    return this.login(stored.userId, stored.role);
  }
  middleware() {
    return (req, res, next) => {
      const token = req.headers.authorization?.split(" ")[1];
      if (!token) return res.status(401).json({ error: "Unauthorized" });
      try {
        req.user = jwt.verify(token, this.secret);
        next();
      } catch { res.status(403).json({ error: "Invalid token" }); }
    };
  }
}

What's Next

Node.js OAuth with Passport Node.js Security Checklist Node.js Authentication

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro