Node.js Guide
Node.js JWT Authentication
Secure your Node.js API with JSON Web Tokens. Sign, verify, and protect routes with production-ready middleware.
Quick answer: Install jsonwebtoken. Sign tokens with jwt.sign(payload, secret, { expiresIn: '1h' }). Verify with jwt.verify(token, secret). Store tokens in HTTP-only cookies, never in localStorage.
Install jsonwebtoken
npm install jsonwebtoken
Store your JWT secret in .env:
JWT_SECRET=your-long-random-secret
JWT_EXPIRES_IN=1h
Generate a strong secret with:
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
Sign a Token on Login
import jwt from 'jsonwebtoken';
app.post('/login', async (req, res) => {
const { email, password } = req.body;
// 1. Find user by email
const user = await User.findOne({ email });
if (!user) return res.status(401).json({ error: 'Invalid credentials' });
// 2. Compare password (bcrypt)
const valid = await bcrypt.compare(password, user.password);
if (!valid) return res.status(401).json({ error: 'Invalid credentials' });
// 3. Sign JWT
const token = jwt.sign(
{ userId: user.id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRES_IN }
);
// 4. Return in HTTP-only cookie
res.cookie('token', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 3600000 // 1 hour
});
res.json({ message: 'Logged in' });
});
Verify Middleware
export function authenticate(req, res, next) {
// Read token from cookie or Authorization header
const token =
req.cookies?.token ||
req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.user = payload; // attach to request
next();
} catch (err) {
return res.status(403).json({ error: 'Invalid or expired token' });
}
}
Protect Routes
import { authenticate } from './middleware/auth.js';
// Public route
app.get('/posts', (req, res) => {
res.json({ posts: [] });
});
// Protected route
app.post('/posts', authenticate, (req, res) => {
console.log('User:', req.user.userId);
// Create the post...
res.status(201).json({ message: 'Created' });
});
// Admin-only route
app.delete('/posts/:id', authenticate, requireAdmin, (req, res) => {
// ...
});
Refresh Tokens (Long Sessions)
Access tokens are short-lived. Issue a separate refresh token to get new ones:
// On login — issue both
const accessToken = jwt.sign(
{ userId: user.id },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
const refreshToken = jwt.sign(
{ userId: user.id },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: '7d' }
);
// Store refresh token in httpOnly cookie
res.cookie('refresh', refreshToken, { httpOnly: true, secure: true });
// Endpoint to refresh
app.post('/refresh', (req, res) => {
const token = req.cookies.refresh;
if (!token) return res.status(401).end();
try {
const { userId } = jwt.verify(token, process.env.JWT_REFRESH_SECRET);
const newAccess = jwt.sign({ userId }, process.env.JWT_SECRET, { expiresIn: '15m' });
res.json({ accessToken: newAccess });
} catch {
res.status(403).end();
}
});
🛡️ Security Best Practices
Never store JWTs in localStorage. localStorage is accessible from any script on the page. An XSS attack steals every token instantly. Use HTTP-only cookies with Secure and SameSite=Strict.
- Use strong secrets. 64 random bytes minimum. Never use "secret" or "123456".
- Short-lived access tokens. 15 minutes to 1 hour. Combine with refresh tokens.
- Always use HTTPS in production. Set the Secure cookie flag.
- Validate the algorithm. Force
algorithms: ['HS256']in verify to prevent algorithm confusion attacks. - Include only non-sensitive data. JWT payloads are base64-encoded, not encrypted — anyone can read them.
- Hash passwords with bcrypt. Never store plain passwords or MD5 hashes.
❓ Frequently Asked Questions
What is JWT in Node.js?
;A compact, self-contained way to transmit authentication info as a JSON object. Standard for stateless APIs.
Should I store JWTs in localStorage?
No. Use HTTP-only cookies with Secure and SameSite flags. localStorage is vulnerable to XSS.
How long should a JWT last?
15 minutes to 1 hour for access tokens. Use refresh tokens for longer sessions.
Can JWTs be revoked?
Not easily — they're stateless. Use short expiry + refresh tokens, or a blacklist in Redis.
Where should the JWT secret go?
In a .env file for local dev, and in the host's environment variables for production. Never commit it to Git.