JWT Best Practices — Production-Ready JSON Web Token Configuration and Usage
In this tutorial, you will learn about JWT Best Practices. We cover key concepts, practical examples, and best practices to help you master this topic.
JWT best practices cover secure key management, algorithm selection, token lifetimes, storage strategies, and common pitfalls to ensure production-grade JWT-based authentication.
What You'll Learn
- Choosing the right signing algorithm for your use case
- Secure key generation, storage, and rotation
- Token lifetime strategies and refresh token rotation
- Secure client-side token storage
- Monitoring and revocation best practices
Why It Matters
Misconfigured JWTs are a leading cause of API security breaches. A single weak key or algorithm confusion vulnerability can compromise your entire auth system. DodaTech uses JWT best practices to secure 200+ API integrations handling 10 million daily token verifications.
Real-World Use
A fintech API uses RS256-signed JWTs with 15-minute access tokens, rotating refresh tokens, and JWKS endpoints. When a developer workstation was compromised, the refresh token rotation limited the Blast Radius to at most one session.
flowchart TD
A["User Login"] --> B["Auth Server
Issues access + refresh tokens"]
B --> C["Access Token
RS256 signed, 15min TTL"]
B --> D["Refresh Token
Rotated on each use"]
C --> E["API Gateway
Verifies signature via JWKS"]
D --> F["Token Rotation
Old refresh invalidated"]
F --> G["New access + refresh issued"]
E --> H["Resource Server
Validates claims"]
Code Examples
Example 1: Secure Key Generation
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
# Generate a strong RSA-4096 key pair
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=4096
)
# Save private key with encryption
pem_private = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.BestAvailableEncryption(b'strong-passphrase')
)
# Save public key
public_key = private_key.public_key()
pem_public = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
print(f"Private key: {len(pem_private)} bytes (encrypted)")
print(f"Public key: {len(pem_public)} bytes")
# Output: Private key: 3342 bytes (encrypted)
# Output: Public key: 800 bytes
Example 2: Token Configuration
import jwt
from datetime import datetime, timedelta, timezone
class JWTConfig:
ALGORITHM = 'RS256'
ACCESS_TTL = timedelta(minutes=15)
REFRESH_TTL = timedelta(days=7)
ISSUER = 'https://auth.dodatech.com'
AUDIENCE = 'https://api.dodatech.com'
def create_access_token(user_id, roles, private_key):
now = datetime.now(timezone.utc)
payload = {
'sub': user_id,
'roles': roles,
'iat': now,
'exp': now + JWTConfig.ACCESS_TTL,
'iss': JWTConfig.ISSUER,
'aud': JWTConfig.AUDIENCE,
'jti': str(uuid.uuid4()),
'type': 'access'
}
token = jwt.encode(payload, private_key, algorithm=JWTConfig.ALGORITHM)
return token
# Usage
token = create_access_token('user_123', ['admin', 'analyst'], private_key)
print(f"Access token: {token[:50]}...")
# Output: Access token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Example 3: Verification with JWKS
import requests
from jwt import PyJWKClient
def verify_token(token):
jwks_url = 'https://auth.dodatech.com/.well-known/jwks.json'
client = PyJWKClient(jwks_url)
try:
signing_key = client.get_signing_key_from_jwt(token)
payload = jwt.decode(
token,
signing_key.key,
algorithms=['RS256'],
audience=JWTConfig.AUDIENCE,
issuer=JWTConfig.ISSUER,
options={
'require': ['exp', 'iat', 'iss', 'aud', 'jti']
}
)
return payload
except jwt.ExpiredSignatureError:
print("Token expired")
except jwt.InvalidAudienceError:
print("Invalid audience")
except Exception as e:
print(f"Verification failed: {e}")
return None
# Verification with JWKS
payload = verify_token(token)
if payload:
print(f"User: {payload['sub']}, Roles: {payload['roles']}")
# Output: User: user_123, Roles: ['admin', 'analyst']
Common Mistakes
1. Using HS256 with Shared Secrets
Use RS256 or ES256 so the signing key stays private on the server. HS256 secrets leak when multiple services need to verify tokens.
2. Not Validating the aud Claim
Any token issued by your auth server should be scoped to a specific audience. Without audience validation, a token for Service A can access Service B.
3. Long-Lived Access Tokens
Access tokens should expire in minutes, not hours or days. Use refresh tokens for longer sessions.
4. Storing Tokens in localStorage
Access tokens in localStorage are accessible to any JavaScript on the page. Use httpOnly cookies for web apps.
5. Not Rotating Refresh Tokens
Every refresh should issue a new refresh token and invalidate the old one. This limits the window for stolen refresh tokens.
Practice Questions
- Why prefer RS256 over HS256 for JWT signing?
- What is the recommended access token lifetime?
- How does refresh token rotation improve security?
- Where should you store JWTs in a browser?
- What claims should always be validated?
Answers:
- RS256 uses asymmetric keys, so only the issuer has the private key. HS256 requires all verifiers to share the secret.
- 5-15 minutes. Short enough to limit breach impact, long enough to avoid excessive refresh overhead.
- Each refresh invalidates the previous refresh token, so a stolen token is only usable once.
- httpOnly cookies to prevent XSS access. Avoid localStorage for access tokens.
exp,iat,iss,aud,sub, andjti(token ID).
Challenge: Build a JWT configuration that uses ES256, 5-minute access tokens, refresh token rotation with reuse detection, and JWKS key rotation. Test with a simulated key compromise.
FAQ
What's Next
Apply best practices to {{< ilink "JWT" "JWT Authentication Service" }} in a capstone project, or explore {{< ilink "JWT" "JWT Security Headers" }} for additional protection.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro