10 Authentication Jwt
title: JWT Authentication in Node.js REST APIs weight: 20 date: 2026-06-28 lastmod: 2026-06-28 description: Learn JWT authentication for Node.js REST APIs including token generation, verification, refresh tokens, blacklisting, and secure storage with Express and Koa. tags: [api-development, nodejs]
JWT (JSON Web Token) authentication for Node.js REST APIs generates signed tokens containing user identity and claims, enabling stateless authentication without server-side sessions and supporting expiration and refresh flows.
```mermaid
flowchart TD
A[Client] -->|POST /auth/login| B[Server]
B --> C{Valid credentials?}
C -->|Yes| D[Generate JWT]
D -->|Access + Refresh Tokens| A
A -->|GET /api/users with Bearer token| E[Auth Middleware]
E --> F{Valid token?}
F -->|Yes| G[Route Handler]
F -->|No| H[401 Unauthorized]
style A fill:#e1f5fe
style D fill:#c8e6c9
style E fill:#fff9c4
style H fill:#ffcdd2
JWT tokens have three parts: header (algorithm), payload (user data, claims, expiration), and signature (verified with secret). Access tokens are short-lived (15-60 minutes). Refresh tokens are long-lived (days) and used to get new access tokens without re-authentication.
Think of JWT like a theme park wristband. The wristband (token) shows who you are (payload), when it expires (exp), and has a hologram (signature) that guards can verify. If your wristband expires, you go to guest services (refresh token endpoint) to get a new one.
Example: Login Endpoint
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
app.post('/api/auth/login', async (req, res, next) => {
try {
const { email, password } = req.body;
// Find user
const user = await findUserByEmail(email);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Verify password
const validPassword = await bcrypt.compare(password, user.password);
if (!validPassword) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Generate tokens
const accessToken = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
const refreshToken = jwt.sign(
{ userId: user.id, tokenVersion: user.tokenVersion },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: '7d' }
);
res.json({
accessToken,
refreshToken,
expiresIn: 900
});
} catch (error) {
next(error);
}
});
Example: Auth Middleware
const authenticate = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
if (error.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Token expired', code: 'TOKEN_EXPIRED' });
}
return res.status(401).json({ error: 'Invalid token' });
}
};
// Protect routes
app.get('/api/users/me', authenticate, (req, res) => {
res.json({ user: req.user });
});
Example: Token Refresh
app.post('/api/auth/refresh', async (req, res, next) => {
try {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(400).json({ error: 'Refresh token required' });
}
// Verify refresh token
const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET);
// Check if token was revoked (user logout or password change)
const user = await findUser(decoded.userId);
if (!user || decoded.tokenVersion !== user.tokenVersion) {
return res.status(401).json({ error: 'Token revoked' });
}
// Issue new access token
const newAccessToken = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
res.json({ accessToken: newAccessToken, expiresIn: 900 });
} catch (error) {
if (error.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Refresh token expired. Login required.' });
}
next(error);
}
});
Common Mistakes
- Storing JWT secret in code — Never hard-code JWT secrets. Use environment variables. Rotate secrets periodically.
- Including sensitive data in payload — JWT payloads are base64-encoded, not encrypted. Do not include passwords or credit card numbers.
- Using long-lived access tokens — Access tokens should expire in minutes, not hours or days. Use refresh tokens for long-lived sessions.
- Not validating token signature — Always verify the signature with jwt.verify(). Accepting unsigned tokens allows anyone to forge identities.
- Storing tokens in localStorage without protection — localStorage is accessible to JavaScript (XSS). Use httpOnly cookies for browser apps when possible.
Practice Questions
- What are the three parts of a JWT token?
- Why should access tokens have short expiration times?
- What is the purpose of a refresh token?
- How do you revoke JWT tokens?
- Challenge: Build a complete JWT authentication system with login, register, token refresh, and logout (token blacklisting). Include password hashing with bcrypt and token version tracking for revocation.
FAQ
Mini Project
Build a complete JWT authentication system for an Express API. Implement register (with bcrypt password hashing), login (returns access + refresh tokens), auth middleware, token refresh endpoint, logout (increments token version), and a protected user profile endpoint.
What's Next
Now learn about authorization middleware in Building REST APIs with Node.js.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro