How to Secure JWT Tokens
JWTs are widely used for authentication but have many security pitfalls. Using weak algorithms, long expiration times, storing tokens insecurely, and missing validation are common mistakes. This guide covers how to implement JWTs securely.
Find security issues automatically before attackers do.
Follow These Steps
Use strong signing algorithms
Always use asymmetric algorithms (RS256) for public-facing APIs or HS256 with a strong secret for internal use.
import jwt from 'jsonwebtoken'
// HS256 with strong secret (minimum 256 bits / 32 bytes)
const token = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET!, // Must be 32+ characters
{ algorithm: 'HS256', expiresIn: '15m' }
)
// Verify with algorithm specified (prevents algorithm confusion)
const decoded = jwt.verify(token, process.env.JWT_SECRET!, {
algorithms: ['HS256'] // Only accept HS256
})Always specify the algorithms array when verifying. This prevents algorithm confusion attacks where an attacker changes the algorithm to "none".
Set short expiration times
Access tokens should expire quickly. Use refresh tokens for longer sessions.
// Access token: 15 minutes
const accessToken = jwt.sign(payload, secret, { expiresIn: '15m' })
// Refresh token: 7 days (stored securely)
const refreshToken = jwt.sign(
{ userId: user.id, type: 'refresh' },
process.env.REFRESH_SECRET!,
{ expiresIn: '7d' }
)Implement token refresh flow
Use refresh tokens to issue new access tokens without re-authentication.
export async function POST(req: Request) {
const { refreshToken } = await req.json()
try {
const decoded = jwt.verify(refreshToken, process.env.REFRESH_SECRET!, {
algorithms: ['HS256']
})
// Check if refresh token is still valid in database
const storedToken = await db.query.refreshTokens.findFirst({
where: eq(refreshTokens.token, refreshToken)
})
if (!storedToken || storedToken.revoked) {
return Response.json({ error: 'Invalid refresh token' }, { status: 401 })
}
// Issue new access token
const newAccessToken = jwt.sign(
{ userId: decoded.userId },
process.env.JWT_SECRET!,
{ expiresIn: '15m' }
)
return Response.json({ accessToken: newAccessToken })
} catch {
return Response.json({ error: 'Invalid token' }, { status: 401 })
}
}Store tokens securely
Store access tokens in memory and refresh tokens in httpOnly cookies.
// Store refresh token in httpOnly cookie (not accessible by JS)
res.cookie('refreshToken', refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/api/auth/refresh',
maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
})
// Store access token in memory (JavaScript variable)
// NOT in localStorage or sessionStorage
let accessToken = response.accessTokenValidate all claims during verification
Check issuer, audience, and other claims to prevent token misuse.
const decoded = jwt.verify(token, secret, {
algorithms: ['HS256'],
issuer: 'your-app',
audience: 'your-api',
clockTolerance: 30 // Allow 30 seconds of clock skew
})What You'll Achieve
Your JWT implementation uses strong algorithms, short expiration times, secure storage, refresh token rotation, and proper claim validation.
Common Mistakes to Avoid
Mistake
Storing JWTs in localStorage
Fix
localStorage is accessible by any JavaScript on the page, making it vulnerable to XSS. Store access tokens in memory and refresh tokens in httpOnly cookies.
Mistake
Not specifying algorithms during verification
Fix
Always pass an algorithms array to jwt.verify(). Without it, attackers can change the algorithm to "none" and forge tokens.
Mistake
Using long expiration times for access tokens
Fix
Access tokens should expire in 15-30 minutes. Use refresh tokens for longer sessions. This limits the window of opportunity if a token is stolen.
Frequently Asked Questions
Should I use JWTs or session cookies?
For most web apps, session cookies (httpOnly, secure) are simpler and more secure. JWTs are useful for stateless APIs, microservices, and mobile apps. Do not use JWTs just because they are popular.
Where should I store the JWT?
Access token: in-memory JavaScript variable. Refresh token: httpOnly secure cookie. Never store tokens in localStorage or sessionStorage.
How do I revoke a JWT?
JWTs are stateless and cannot be revoked directly. Use short expiration times (15 min) and a revocation list or database check for refresh tokens. Alternatively, use session-based auth for easy revocation.
Ready to Secure Your App?
VAS automatically scans your deployed app for the security issues covered in this guide. Get actionable results in minutes.
Start Security Scan