Node.js JWT Authentication — Complete Guide to JSON Web Tokens
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
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