Cybersecurity
Redefining Security: JWT Best Practices for Modern Distributed Architectures
JSON Web Tokens have become the backbone of stateless authentication across distributed systems, microservices, and SPAs. Their portability is genuinely compelling — a signed token carries all the claims needed to authorize a request without any round-trip to a session store. But this simplicity hides a set of subtle, high-impact vulnerabilities that have burned teams shipping real products at scale. This article cuts through the surface-level advice and focuses on the practices that actually matter in production environments handling millions of requests per day.
Understanding What JWTs Actually Guarantee
A JWT is a compact, URL-safe token made of three Base64URL-encoded parts: a header, a payload, and a signature. The header declares the algorithm used; the payload contains claims (arbitrary key-value pairs); the signature binds them together using a secret or a private key. What a signed JWT guarantees is integrity — if the token has not been tampered with, the signature will verify. What it does not guarantee is confidentiality. The payload is readable by anyone who intercepts the token.
Technical Insight: Because Base64URL is encoding, not encryption, any claim in your JWT payload is visible in plaintext to the browser, to intermediary proxies, and to anyone performing a MITM attack over HTTP. If you need confidentiality, you require JSON Web Encryption (JWE), not JWS. Most teams ship JWS and assume confidentiality — that is a critical design error.
Practical Scenario: An enterprise SaaS platform embedded the user's subscription tier in the payload to speed up authorization checks. After a penetration test, they discovered that any authenticated user could inspect their own JWT and see exact pricing thresholds used for feature gating — information the sales team considered confidential. The fix was straightforward (move sensitive metadata to a server-side lookup), but the trust damage in the incident report was not.
The Algorithm Confusion Attack: Why 'none' Is Never Acceptable
The JWT specification allows libraries to accept the none algorithm, intended for use cases where the token integrity is guaranteed by an external channel. In practice, this has led to one of the most widely exploited JWT vulnerabilities ever documented. The attack is straightforward: an attacker takes a legitimate JWT, changes the algorithm header to none, removes the signature, and submits it. If the library trusts the header and skips signature verification when alg is none, the attacker has just escalated their privileges without breaking any cryptographic primitive.
The related attack is algorithm confusion between symmetric and asymmetric keys. A library configured for RS256 (asymmetric) may accept a token signed with HS256 (symmetric) if the developer passes the public key as the HMAC secret. The attacker obtains the public key (which is meant to be distributed), uses it as the HS256 secret to sign a forged payload, and presents it to the server — which verifies it successfully.
Technical Insight: Mitigation requires strict algorithm allowlisting on the verification side. Do not pass the algorithm from the token header to the verification function. Instead, hardcode the expected algorithm in server configuration. This is now a requirement in most compliant libraries post-RFC 8725, but older dependencies may still expose it.
Common Mistake: Using library defaults without auditing the algorithm enforcement path. Before pinning library versions, verify the verification code path explicitly rejects tokens whose alg header does not match your configured expectation.
Key Management: RS256 vs ES256 and Rotation Policies
Symmetric signing (HS256) requires the same secret for both signing and verification. This is acceptable for monolithic applications where both operations happen in trust boundaries you fully control. The moment a second service needs to verify tokens — without the ability to issue them — you have a key distribution problem. Both parties must share the secret, and any leak compromises the entire system.
Asymmetric algorithms solve this by splitting the signing key (private, kept only on the Auth Server) from the verification key (public, distributed to any relying party). RS256 uses 2048-bit RSA and is the current baseline. ES256 uses elliptic curve cryptography (P-256) and produces smaller signatures with equivalent or better security — a relevant optimization when tokens are included in every HTTP request header.
| Property | HS256 (HMAC) | RS256 (RSA) | ES256 (ECDSA) |
|---|---|---|---|
| Key type | Symmetric | Asymmetric | Asymmetric |
| Signature size | 32 bytes | 256 bytes | 64 bytes |
| Distributed verification | No — secret must be shared | Yes — public key | Yes — public key |
| Performance (verify) | Very fast | Medium | Fast |
| Recommended for microservices | No | Yes | Yes (preferred) |
Key rotation is non-negotiable in a mature system. Rotating asymmetric keys requires publishing the new public key via a JWKS endpoint before the old private key is retired. Clients that cache the old public key for performance will fail validation if the rotation is not gradual. A common pattern is to publish the new key alongside the old one with a kid (key ID) claim, allowing clients to select the right verification key per token until the old key is fully retired.
Token Storage in SPAs: The httpOnly Cookie Pattern
The de facto bad practice in SPAs is storing the access token in localStorage. Any third-party script included in your page — an analytics library, a CDN-hosted widget, a compromised npm dependency — can execute localStorage.getItem('access_token') and exfiltrate it silently. XSS vulnerabilities, which are still extraordinarily common in complex front-end applications, directly translate to token theft when localStorage is used.
The recommended pattern stores the access token in memory (a JavaScript variable in the application state) and stores the refresh token in an httpOnly, Secure, SameSite=Strict cookie. This configuration means the refresh token is never accessible to JavaScript — even your own — and the browser automatically sends it only over HTTPS to the exact domain that issued it.
Architectural Consideration: This pattern requires a lightweight server-side token refresh endpoint. When the access token expires (typically after 15 minutes), the SPA calls this endpoint with the httpOnly cookie attached automatically by the browser, and receives a new access token in the response body. The refresh token itself gets rotated on every use, making token replay attacks significantly harder to execute undetected.
- Access token lifetime: 5–15 minutes. Short enough to limit blast radius on leakage.
- Refresh token lifetime: 7–30 days with rotation on use.
- Store access tokens in memory only, never in localStorage or sessionStorage.
- Store refresh tokens in httpOnly Secure SameSite=Strict cookies.
- Implement a token revocation list (Redis-backed) for cases where immediate invalidation is required (password change, account compromise).
Claim Validation: Beyond Signature Verification
A valid signature means only that the token was issued by an entity holding the private key. It says nothing about whether that token is meant for your service, whether it has expired, or whether it has been revoked. Claim validation is a mandatory second step that many teams skip or implement incompletely.
The minimum required validations are: exp (expiration — reject tokens whose exp is in the past), iss (issuer — reject tokens not issued by your expected Auth Server), and aud (audience — reject tokens not intended for your service). The aud claim is consistently underimplemented, leading to token reuse across services within the same organization. A token issued for the billing service should be rejected by the inventory service even if both trust the same Auth Server.
Real-world Architecture Scenario: A fintech platform running 12 microservices shared a single Auth Server. All services correctly validated iss and exp, but none validated aud. An attacker obtained a valid token for the low-permission public API and replayed it against the internal admin service. Since both services trusted the same issuance authority and no audience restriction was enforced, the attack succeeded. Implementing strict aud validation across all services resolved the incident.
FAQ
- Can I revoke a JWT before it expires? Not natively. The standard approach is a short expiry combined with a Redis-backed revocation list queried on each request for sensitive operations.
- Should I store user roles in the JWT payload? Only stable, low-sensitivity authorization data (user ID, role). Avoid embedding fine-grained permissions that change frequently — stale claims create authorization drift.
- Is JWT better than sessions for microservices? For inter-service communication where a session store would require shared infrastructure across teams, yes. For traditional monoliths, opaque session IDs with server-side lookup are often simpler and safer.
- What is token introspection and when should I use it? Token introspection (RFC 7662) allows a resource server to validate a token by querying the Auth Server at request time. It eliminates stale claim problems but reintroduces network coupling. Use it for high-sensitivity endpoints where freshness guarantees justify the latency.
- How should I handle key compromise? Immediately rotate the signing key, invalidate all outstanding tokens by incrementing a global version claim or purging the revocation list seed, and force re-authentication across all sessions.
Conclusion
JWT-based authentication is not inherently insecure, but it requires deliberate engineering decisions at every layer: algorithm enforcement, key management, claim validation, and client-side storage. The vast majority of real-world JWT breaches stem from ignoring one of these layers — not from weaknesses in the standard itself. As distributed architectures become the norm for even mid-sized products, getting these fundamentals right is no longer optional. The teams that invest in a properly hardened token infrastructure early save themselves from incident responses that are orders of magnitude more expensive than the upfront effort.
Share this post
Subscribe
Get the latest posts delivered right to your inbox.
Leave a comment