Skip to content

JWT Best Practices — Production-Ready JSON Web Token Configuration and Usage

DodaTech Updated 2026-06-28 4 min read

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

  1. Why prefer RS256 over HS256 for JWT signing?
  2. What is the recommended access token lifetime?
  3. How does refresh token rotation improve security?
  4. Where should you store JWTs in a browser?
  5. What claims should always be validated?

Answers:

  1. RS256 uses asymmetric keys, so only the issuer has the private key. HS256 requires all verifiers to share the secret.
  2. 5-15 minutes. Short enough to limit breach impact, long enough to avoid excessive refresh overhead.
  3. Each refresh invalidates the previous refresh token, so a stolen token is only usable once.
  4. httpOnly cookies to prevent XSS access. Avoid localStorage for access tokens.
  5. exp, iat, iss, aud, sub, and jti (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

How often should I rotate signing keys?

: Every 3-6 months, or immediately after a suspected compromise. Keep the old key in a JWKS cache for the token expiry window.

Can I use JWTs for sessions?

: Yes, but prefer server-side sessions for high-security apps where immediate revocation is required.

What is token reuse detection?

: When a refresh token is used after rotation, the old token is flagged as potentially stolen and the session is invalidated.

Should I encrypt JWTs?

: Only if they contain sensitive data. Signed JWTs prevent tampering but don't hide the payload.

How do I handle clock skew?

: Set a small leeway (e.g., 30 seconds) when validating exp and iat.

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