Security for Legal SaaS

Episode 18 · Module 5 · Authentication & Identity

JWT Anatomy and Pitfalls

18 May 2026 · 9:53 · Security for Legal SaaS

9:53 9:53

JSON Web Tokens transformed web authentication — and introduced a new class of vulnerabilities. Alice and Dan dissect the three-part JWT structure (header, payload, signature), the infamous “none” algorithm attack, algorithm confusion (RS256 to HS256), token lifetime tradeoffs, the access-plus-refresh pattern, what JWTs fundamentally cannot do (immediate revocation), and when server-side sessions are the better choice for legal SaaS handling privileged documents.

Today’s Lesson

Security for Legal SaaS — Episode 18: JWT Anatomy and Pitfalls

The Token That Changed Web Authentication

JSON Web Tokens transformed how web applications handle authentication. Instead of server-side session storage, a JWT encodes identity claims into a self-contained, cryptographically signed token that the client carries. RFC 7519 defines the standard — and since its publication in 2015, JWTs have become the default authentication mechanism for SPAs (Single-Page Applications — web apps where the entire interface runs in the browser), mobile apps, and API services.

Key insight: JWTs shift trust from server state to cryptographic verification. This is powerful — and dangerous. Every vulnerability in JWT implementation is a vulnerability in your entire authentication layer. PortSwigger’s JWT security research documents over a dozen exploitable attack patterns.

For legal SaaS, where multi-tenant isolation and privilege boundaries are existential concerns, understanding exactly what JWTs guarantee — and what they cannot — is the difference between secure architecture and a breach waiting for a trigger.

JWT Structure — Three Parts, Base64-Encoded

A JWT consists of three Base64URL-encoded segments separated by dots: header.payload.signature.

Header

{
  "alg": "RS256",
  "typ": "JWT",
  "kid": "2024-signing-key-01"
}

The header declares the signing algorithm and optionally a key ID for key rotation. RFC 7518 (JSON Web Algorithms) defines the allowed algorithms.

Payload (Claims)

{
  "sub": "user_8f3a2b",
  "iss": "https://auth.legalplatform.com",
  "aud": "https://api.legalplatform.com",
  "iat": 1716000000,
  "exp": 1716003600,
  "tenant_id": "firm_baker_mckenzie",
  "role": "partner",
  "scope": "documents:read documents:write matters:read"
}
Claim Purpose Security Implication
subSubject (user ID)Must be opaque — never use email addresses
issIssuerVerify on every request — reject tokens from unexpected issuers
audAudienceThe service this token is intended for — reject mismatched audiences
expExpirationEnforce strictly — no grace periods beyond clock skew
iatIssued atEnables detection of tokens issued before a security event
tenant_idCustom: tenant isolationEnforce at API layer — never trust the token alone for tenant boundaries
scopePermissionsPrinciple of least privilege — request minimum needed

Signature

The signature is computed over the header and payload: HMAC-SHA256(base64url(header) + "." + base64url(payload), secret) for symmetric algorithms, or an RSA/ECDSA signature for asymmetric ones.

The “none” Algorithm Attack

Critical vulnerability: Auth0’s 2015 disclosure revealed that multiple JWT libraries accepted tokens with "alg": "none" — the “unsecured JWS” defined in RFC 7519. An attacker strips the signature entirely, sets the algorithm to “none,” modifies any claims they want (escalate role, change tenant_id), and the server accepts it.

How it works:

  1. Attacker intercepts a valid JWT
  2. Decodes the header, changes "alg": "RS256" to "alg": "none"
  3. Modifies payload claims (elevate privileges, switch tenants)
  4. Removes the signature segment
  5. Sends: eyJ...modified_header.eyJ...modified_payload.

Defence: Never allow the token to specify its own algorithm. Configure your verification library with a hardcoded expected algorithm. Reject any token whose header algorithm doesn’t match. Most modern libraries do this by default, but legacy code and misconfiguration remain common.

Algorithm Confusion (RS256 → HS256)

A subtler variant: if a server uses RS256 (asymmetric — signs with private key, verifies with public key), an attacker can:

  1. Obtain the public key (often exposed at /.well-known/jwks.json — JWKS being the JSON Web Key Set, a standard endpoint where servers publish their public signing keys)
  2. Set the header algorithm to HS256 (symmetric)
  3. Sign the modified token using the public key as the HMAC secret
  4. If the server blindly trusts the algorithm header, it verifies using the public key as an HMAC secret — and the signature passes

Tim McLean’s original research demonstrated this across multiple JWT libraries. The fix: always specify the expected algorithm in your verification call.

Token Lifetime Tradeoffs

Parameter Short-Lived (5–15 min) Long-Lived (hours/days)
Exposure windowMinimalDangerous
User frictionFrequent re-auth (mitigated by refresh tokens)Seamless
Revocation needLow — tokens expire naturallyHigh — can’t wait for expiry
Storage riskLower impact if stolenCatastrophic if stolen

Access + Refresh Token Pattern

OAuth 2.0 (RFC 6749) established the pattern:

For legal SaaS: access token at 15 minutes (short enough that revocation is rarely needed), refresh token at 7 days (stored in httpOnly cookie, rotated on each use). OWASP recommends refresh token rotation — if a refresh token is used twice (indicating theft), invalidate all tokens for that user immediately.

What JWTs Cannot Do

Immediate Revocation

JWTs are verified without contacting the server. Once issued, they’re valid until expiry. You cannot “delete” a JWT. This creates a fundamental problem: if a user’s account is compromised, if they’re deprovisioned, if their permissions change — the existing JWT remains valid until it naturally expires.

Strategy Latency Cost
Short expiry (5 min)5 min max exposureFrequent refreshes
Token blocklist (Redis/memory)Near-instantRequires server state — partially defeats the purpose
Token versioning (per-user counter)On next verificationRequires database lookup — partially defeats the purpose
Event-driven invalidationSeconds (pub/sub)Infrastructure complexity

Server-Side State

The promise of JWTs is statelessness. But real applications need revocation, audit logging, concurrent session limits, and permission changes to take effect immediately. Thomas Ptacek’s critique: “If you need server-side state anyway (and you do), you might as well use sessions.”

Pragmatic guidance: Use JWTs for stateless service-to-service authentication where revocation is less critical. Use stateful sessions (server-side session ID in a cookie) for user-facing authentication where you need immediate revocation, concurrent session control, and activity tracking. Many production systems use both — JWT between microservices (small, independent services that each handle one function), sessions for the user-facing layer.

When to Use JWTs vs Stateful Sessions

Criterion JWT Stateful Session
Immediate revocation neededNo — use sessionsYes
Distributed microservicesYes — no shared session store neededRequires distributed cache
Concurrent session limitsDifficultNative
Audit trail of active sessionsDifficultNative
ScalabilityExcellent — no server stateRequires session store scaling
Offline/mobileGood — token is self-containedRequires connectivity
Sensitive legal operationsConsider sessionsPreferred

OWASP’s Session Management Cheat Sheet recommends server-side sessions for applications requiring “immediate invalidation capability” — which describes every legal SaaS platform handling privileged documents.

JWT Security Checklist for Legal SaaS

Control Implementation
Algorithm hardcodingVerify with explicit algorithms=["RS256"] — never trust the header
Signature verificationVerify every token on every request — no exceptions
Claims validationCheck iss, aud, exp, iat on every verification
Key managementRotate signing keys annually; support multiple kid values during rotation
Token storage (browser)httpOnly, Secure, SameSite=Strict cookies — never localStorage
Refresh token rotationNew refresh token on each use; detect reuse as compromise
Scope minimisationIssue tokens with minimum required permissions
Tenant isolationValidate tenant_id claim against the resource being accessed — defence in depth
Clock skewMaximum 30 seconds tolerance for exp verification
Token sizeKeep payloads minimal — JWTs travel with every request

The jwt.io debugger is invaluable for inspecting tokens during development — but note that pasting production tokens into third-party websites exposes their contents. Decode locally with base64 -d for production tokens.

Real-World JWT Vulnerabilities

In 2022, researchers discovered that Microsoft Azure AD tokens could be forged due to a validation bypass — the Storm-0558 incident where a stolen signing key allowed forging tokens for any Azure AD user. The Auth0 “algorithm confusion” disclosure affected libraries in Java, Node.js, PHP, and Python simultaneously.

For legal SaaS, a forged JWT with an elevated tenant_id or role claim means an attacker can access another firm’s privileged documents. The cryptographic signature is the only barrier — and it’s only as strong as your algorithm choice, key management, and verification implementation.

RFC 9068 (JWT Profile for OAuth 2.0 Access Tokens) standardises the claims structure for access tokens, providing interoperability guidelines that reduce implementation errors.

Conclusion

JWTs are powerful but unforgiving. The “none” algorithm attack, algorithm confusion, and the fundamental inability to revoke issued tokens make JWT security a matter of precise implementation, not conceptual understanding. Hardcode your algorithms. Validate every claim. Keep access tokens short-lived. Use refresh token rotation. And ask the honest question: does your application actually need stateless tokens, or would server-side sessions — simpler, revocable, auditable — serve your security requirements better?

For legal SaaS handling privileged communications across multi-tenant boundaries, the answer is often both: sessions for user-facing authentication, JWTs for internal service communication.

Next episode: Session Management — the server-side approach, done right: from ID generation through lifecycle to invalidation.

Sources & references

  1. IETF, RFC 7519: JSON Web Token (JWT)
  2. PortSwigger, "JWT Security." Comprehensive attack taxonomy
  3. IETF, RFC 7518: JSON Web Algorithms (JWA)
  4. Auth0, "Critical Vulnerabilities in JSON Web Token Libraries," 2015. Algorithm confusion and "none" algorithm attacks
  5. Tim McLean, "JWT Algorithm Confusion," 2015. RS256/HS256 attack
  6. IETF, RFC 6749: The OAuth 2.0 Authorization Framework
  7. OWASP, "JSON Web Token for Java Cheat Sheet." Refresh token rotation
  8. Thomas Ptacek, commentary on JWTs vs sessions (Hacker News)
  9. OWASP, "Session Management Cheat Sheet"
  10. Auth0, jwt.io — JWT debugger and library directory
  11. Wiz Research, "Storm-0558: Compromised Microsoft Key Was More Potent Than We Thought," 2023. Azure AD token forging
  12. IETF, RFC 9068: JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens