JWT Security: Vulnerabilities, Best Practices & Implementation Guide

SCR Team
February 15, 2026
15 min read
598 words
Share

JWT Security in 2025: Complete Guide to Vulnerabilities & Best Practices

JSON Web Tokens (JWTs) power authentication in 94% of modern APIs (Source: Postman State of API Report, 2024). Yet JWT misconfigurations remain among the top API security risks (OWASP API Top 10). This guide provides research-backed best practices, real breach case studies, and production-ready code.


What is a JWT? Token Anatomy Explained

A JWT consists of three Base64-encoded parts separated by dots:

┌────────────────────────────────────────────────────────────────┐
│  eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9                          │ ◄── Header
│  .                                                              │
│  eyJzdWIiOiIxMjM0NTY3ODkwIiwiZW1haWwiOiJ1c2VyQGV4YW1wbGUuY29tIn │ ◄── Payload
│  .                                                              │
│  SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c                   │ ◄── Signature
└────────────────────────────────────────────────────────────────┘
{
  "alg": "RS256",
  "typ": "JWT"
}

Payload (Claims)

{
  "sub": "user123",
  "email": "user@example.com",
  "iat": 1708012800,
  "exp": 1708013700,
  "aud": "api.example.com",
  "iss": "auth.example.com"
}

Signature

RSASHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), privateKey)

JWT Security Statistics (2025)

MetricValueSource
APIs using JWT94%Postman 2024
JWT-related breaches (2024)23% of auth breachesVerizon DBIR
Avg. breach cost (auth failures)$4.76MIBM Cost of Data Breach 2024
Time to exploit weak JWT< 2 hoursAuth0 Security Research

Top 5 JWT Vulnerabilities & How to Fix Them

1. Algorithm Confusion Attack (CVE-2015-9235)

The Problem: Attacker changes alg header from RS256 to HS256, using the public key as the HMAC secret.

// ❌ VULNERABLE: Accepts any algorithm
const decoded = jwt.verify(token, publicKey);
// ✅ SECURE: Whitelist algorithms
const decoded = jwt.verify(token, publicKey, {
  algorithms: ['RS256']  // ONLY RS256 allowed
});

Real Breach: Auth0 libraries were vulnerable until 2015. Attackers forged admin tokens.


2. Missing Token Expiration

The Problem: Tokens without exp claim never expire—compromised tokens are valid forever.

// ❌ DANGEROUS: No expiration
jwt.sign({ userId: 123 }, secret);

// ✅ SECURE: Short-lived tokens
jwt.sign({ userId: 123 }, secret, { expiresIn: '15m' });

Best Practice:

  • Access tokens: 15-30 minutes
  • Refresh tokens: 7 days (stored securely)

3. Token Storage in localStorage (XSS Vulnerability)

The Problem: JavaScript can access localStorage—XSS attacks steal tokens.

┌─────────────────────────────────────────────────────────────────┐
│  XSS Attack Flow                                                │
│                                                                 │
│  1. Attacker injects malicious script                           │
│  2. Script reads localStorage.getItem('token')                  │
│  3. Token exfiltrated to attacker server                        │
│  4. Attacker impersonates victim                                │
└─────────────────────────────────────────────────────────────────┘
// ❌ VULNERABLE: XSS can steal this
localStorage.setItem('token', jwt);

// ✅ SECURE: httpOnly cookies (JS cannot access)
res.cookie('token', jwt, {
  httpOnly: true,    // Cannot be read by JavaScript
  secure: true,      // HTTPS only
  sameSite: 'strict' // CSRF protection
});

4. HS256 with Weak/Leaked Secrets

The Problem: HS256 uses symmetric keys. If secret leaks, attacker forges any token.

// ❌ WEAK: Short, guessable secret
jwt.sign(payload, 'secret123');

// ❌ LEAKED: Secret in source code
const SECRET = 'production-jwt-secret-2024';

// ✅ SECURE: RS256 (asymmetric)
jwt.sign(payload, privateKey, { algorithm: 'RS256' });

Key Comparison:

AlgorithmKey TypeBest ForRisk
HS256SymmetricSingle serverSecret leak = full compromise
RS256AsymmetricDistributed systemsPublic key is safe to share
ES256Asymmetric (ECDSA)Mobile/IoTSmaller keys, same security

5. Missing Claim Validation

The Problem: Accepting tokens without validating aud, iss, or custom claims.

// ❌ VULNERABLE: No claim validation
const decoded = jwt.verify(token, secret);

// ✅ SECURE: Full validation
const decoded = jwt.verify(token, publicKey, {
  algorithms: ['RS256'],
  audience: 'api.example.com',
  issuer: 'auth.example.com',
  clockTolerance: 30  // 30 seconds clock skew tolerance
});

// Additional validation
if (!decoded.userId || !decoded.email) {
  throw new Error('Invalid token claims');
}

Refresh Token Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                     Refresh Token Flow                               │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  Client                    Server                    Database        │
│    │                         │                           │           │
│    │──── Login ─────────────►│                           │           │
│    │                         │──── Store Refresh ───────►│           │
│    │◄─── Access + Refresh ───│                           │           │
│    │                         │                           │           │
│    │─── API Request ────────►│                           │           │
│    │     (Access Token)      │                           │           │
│    │◄───── Response ─────────│                           │           │
│    │                         │                           │           │
│    │  [Access Token Expires] │                           │           │
│    │                         │                           │           │
│    │─── Refresh Request ────►│                           │           │
│    │    (Refresh Token)      │──── Validate ────────────►│           │
│    │                         │◄─── Valid ────────────────│           │
│    │◄─── New Access Token ───│──── Rotate Refresh ──────►│           │
│    │                         │                           │           │
└─────────────────────────────────────────────────────────────────────┘

Implementation

// Login: Issue both tokens
app.post('/login', async (req, res) => {
  const user = await authenticate(req.body);
  
  const accessToken = jwt.sign(
    { userId: user.id, email: user.email },
    privateKey,
    { algorithm: 'RS256', expiresIn: '15m' }
  );
  
  const refreshToken = jwt.sign(
    { userId: user.id, tokenVersion: user.tokenVersion },
    privateKey,
    { algorithm: 'RS256', expiresIn: '7d' }
  );
  
  // Store refresh token hash in DB for revocation
  await storeRefreshToken(user.id, refreshToken);
  
  res.cookie('refreshToken', refreshToken, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    path: '/api/refresh'  // Only sent to refresh endpoint
  });
  
  res.json({ accessToken });
});

// Refresh: Issue new access token
app.post('/api/refresh', async (req, res) => {
  const refreshToken = req.cookies.refreshToken;
  
  const decoded = jwt.verify(refreshToken, publicKey, {
    algorithms: ['RS256']
  });
  
  // Check if token is revoked
  if (await isTokenRevoked(decoded.userId, refreshToken)) {
    return res.status(401).json({ error: 'Token revoked' });
  }
  
  const newAccessToken = jwt.sign(
    { userId: decoded.userId },
    privateKey,
    { algorithm: 'RS256', expiresIn: '15m' }
  );
  
  res.json({ accessToken: newAccessToken });
});

JWT Security Checklist

  • Use RS256 or ES256 (asymmetric) over HS256
  • Set short expiration (15-30 min for access tokens)
  • Store tokens in httpOnly cookies (not localStorage)
  • Implement refresh token rotation
  • Whitelist algorithms in verify options
  • Validate all claims (exp, iat, aud, iss)
  • Use HTTPS everywhere
  • Implement token revocation for logout
  • Rotate signing keys quarterly
  • Rate limit token endpoints
  • Monitor for anomalous token patterns
  • Never log or expose tokens in errors

Real-World JWT Breaches

1. Auth0 Algorithm Confusion (2015)

  • Vulnerability: Library accepted HS256 when expecting RS256
  • Impact: Token forgery possible with public key
  • Fix: Strict algorithm whitelisting

2. Zoom JWT Vulnerabilities (2020)

  • Vulnerability: No audience validation, long-lived tokens
  • Impact: Meeting hijacking
  • Fix: Short expiration + audience validation

3. Palo Alto PAN-OS (2024, CVE-2024-0012)

  • Vulnerability: JWT authentication bypass
  • Impact: Admin access without credentials
  • Fix: Proper signature verification

JWT vs Session Comparison

FeatureJWTSessions
Server storageNone (stateless)Required
ScalabilityExcellentRequires shared store
RevocationComplexImmediate
Mobile/APIIdealRequires cookies
Token sizeLarger (payload)Small (session ID)
Best forAPIs, microservicesTraditional web apps

Tools & Resources

ToolPurposeURL
jwt.ioToken debuggerhttps://jwt.io
jwt_toolPenetration testingGitHub
joseJS JWT librarynpmjs.com/jose
PyJWTPython librarypypi.org/project/PyJWT

SEO Summary & Key Takeaways

  1. Use RS256/ES256 — Asymmetric algorithms prevent secret leakage
  2. 15-minute expiration — Limit blast radius of stolen tokens
  3. httpOnly cookies — Eliminate XSS token theft
  4. Whitelist algorithms — Prevent algorithm confusion attacks
  5. Validate all claims — aud, iss, exp are mandatory

Next steps: Audit your JWT implementation against the checklist above. Use jwt.io to decode and inspect your tokens (never in production!).


Last updated: February 2025 | Sources: OWASP, Auth0, RFC 7519, Postman API Report 2024

Editorial standards

Published by SecureCodeReviews

This article is part of our original AI security and cybersecurity content library. We show publish and update dates, keep company and policy pages public, and update important guidance when material changes affect readers.

Named author: SCR Team
Published: Feb 15, 2026
Update status: current publication version

Questions or corrections?

Review our editorial standards, learn more about the company, or contact us if a page needs clarification.

API Security Review

Shipping an API that needs a hard security pass?

We review authentication, authorization, business logic abuse, rate limiting, and the edge cases automated scanners usually miss.

OWASP API risk coverage and business logic testing
Auth and access control review with practical fixes
Clear technical findings your team can act on fast

Talk to SecureCodeReviews

Get a scoped review path fast

Manual review
Actionable fixes
Fast turnaround
Security-focused

Advertisement