12 min read

Guide · Developer security

JWT Security Best Practices

Everything you need to know about implementing JSON Web Tokens securely — algorithms, secret keys, common attacks, and safe storage.

Understanding JWT security

JSON Web Tokens (JWTs) are widely used for authentication and authorization, but they are also frequently misconfigured. This guide covers the essential security practices for working with JWTs.

Important: JWTs are signed, not encrypted. Anyone can read the payload — the signature only ensures it has not been tampered with. Never put sensitive data in a JWT without additional encryption.

Choosing the right algorithm

Symmetric algorithms (HMAC)

Use HMAC algorithms when the same party signs and verifies tokens.

HS256HMAC with SHA-256 (256-bit key minimum)
HS384HMAC with SHA-384 (384-bit key minimum)
HS512HMAC with SHA-512 (512-bit key minimum)

Asymmetric algorithms (RSA / ECDSA)

Use asymmetric algorithms when different parties sign and verify tokens, such as across microservices.

RS256RSA with SHA-256 (2048-bit key minimum)
ES256ECDSA with P-256 curve (recommended)
ES384ECDSA with P-384 curve
Recommendation: for most applications, use HS256 with a 256-bit secret, or ES256 for asymmetric signing. ES256 offers strong security with smaller keys and signatures than RSA.

Secret key requirements

For HMAC algorithms, your secret key should be:

  • At least as long as the hash output (256 bits for HS256)
  • Generated using a cryptographically secure random number generator
  • Unique per environment (development, staging, and production)
Terminal
# Good: generate a secure 256-bit secret
openssl rand -base64 32

# Example output (DO NOT USE THIS!)
# K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=
Never use: short secrets, dictionary words, application names, or predictable values such as "secret", "jwt-secret", or "your-256-bit-secret".

Need a key right now? Generate a JWT secret — created locally, never transmitted.

Common vulnerabilities & prevention

1Algorithm confusion (alg=none)

Attackers may try to change the algorithm to none or switch between symmetric and asymmetric algorithms.

Node.js
// VULNERABLE: accepts any algorithm
jwt.verify(token, secret);

// SECURE: explicitly specify allowed algorithms
jwt.verify(token, secret, { algorithms: ['HS256'] });

2Missing signature verification

Always verify the signature before trusting token contents.

Node.js
// VULNERABLE: decodes without verification
const payload = jwt.decode(token);

// SECURE: verifies signature first
const payload = jwt.verify(token, secret);

3Key confusion attack

When using RS256, attackers may try to verify with the public key as an HMAC secret.

Node.js
// VULNERABLE: could be tricked into HMAC verification
jwt.verify(token, publicKey);

// SECURE: explicitly require RS256
jwt.verify(token, publicKey, { algorithms: ['RS256'] });

4Missing expiration

Tokens without expiration never become invalid.

Node.js
// Always include expiration
const token = jwt.sign(
  { userId: 123 },
  secret,
  { expiresIn: '1h' }  // or use 'exp' claim directly
);

Token validation checklist

Always validate these claims when verifying a JWT:

SignatureMust be valid for the specified algorithm
expToken must not be expired
nbfToken must be active (not before)
issMust match the expected issuer
audMust include your application (audience)
iatIssued-at should not be in the future
Node.js
// Comprehensive validation
jwt.verify(token, secret, {
  algorithms: ['HS256'],
  issuer: 'https://yourapp.com',
  audience: 'your-api',
  clockTolerance: 30,  // 30 second clock skew
});

Token lifetime & refresh

Access tokens15 minutes to 1 hour
Refresh tokens7–30 days (stored securely)
ID tokens5–15 minutes

Refresh token strategy

  • Use short-lived access tokens with longer-lived refresh tokens
  • Store refresh tokens securely in HttpOnly cookies or secure storage
  • Rotate refresh tokens by issuing a new one on every use
  • Maintain a token blacklist or use token families for revocation

Storage best practices

In browser applications, where you keep a token determines how exposed it is. Options ranked by security:

1HttpOnly cookies — best protection against XSS
2In-memory — good security, but lost on refresh
3sessionStorage — tab-specific and cleared on close
4localStorage — vulnerable to XSS (avoid)
Cookie configuration
// Secure cookie settings
res.cookie('token', jwt, {
  httpOnly: true,      // prevents JavaScript access
  secure: true,        // HTTPS only
  sameSite: 'strict',  // CSRF protection
  maxAge: 3600000,     // 1 hour
  path: '/',
});

Token revocation

JWTs are stateless by design, making revocation challenging. Consider these approaches:

  • Short expiration — tokens naturally expire quickly
  • Token blacklist — store revoked token IDs using the jti claim
  • Token versioning — increment the user's token version on logout
  • Refresh token revocation — revoke refresh tokens to prevent new access tokens
Pro tip: for high-security applications, consider opaque tokens that reference server-side sessions. This enables instant revocation at the cost of a data lookup per request.

Implementation checklist

Track your progress0 of 10