How JWT Authentication Works
End-to-end flow, signing algorithms, token lifetimes, and the vulnerabilities that trip up most implementations.
Part of the API Authentication Methods guide series.
What Is a JWT?
A JSON Web Token (JWT) is a compact, URL-safe token that encodes claims — statements about a user or system — as a signed JSON object. Unlike opaque session tokens that require a database lookup, a JWT carries its own data. Any service that holds the signing key can validate a JWT without calling the issuer.
JWTs are defined in RFC 7519 and used across OAuth 2.0, OpenID Connect, and countless custom authentication systems. They are stateless by design: the server stores no session state; all necessary information travels in the token itself.
Red = header · Purple = payload · Cyan = signature. Each part is base64url-encoded and joined with dots.
The Three Parts of a JWT
| Part | Contains | Example (decoded) |
|---|---|---|
| Header | Algorithm (alg) and token type (typ) | {"alg":"HS256","typ":"JWT"} |
| Payload | Claims: sub, iat, exp, custom fields | {"sub":"user_123","role":"admin","exp":1700003600} |
| Signature | HMAC or RSA/EC signature over header + payload | SflKxwRJSMeKKF2QT4fw… (base64url) |
JWTs are encoded, not encrypted. The payload is base64url-decoded by anyone — no secret required. Never put passwords, PII, or confidential data in a JWT payload unless you use JWE (JSON Web Encryption). Use JWT Token Validator to decode any JWT instantly.
End-to-End Authentication Flow
User logs in
The client sends credentials (username + password, or OAuth code) to the authentication server.
Server creates and signs the JWT
The server builds a payload with claims (sub, iat, exp, roles, scopes) and signs it with the secret key or private key. The resulting token is sent back to the client.
Client stores the token
Typically in memory (most secure), an HttpOnly cookie (good for web), or localStorage (avoid — vulnerable to XSS). The choice affects what attack surface you expose.
Client sends the JWT on each request
The token is attached to the Authorization header: Authorization: Bearer <token>. No session cookie is needed.
Server validates the JWT
The server (or API gateway) verifies the signature using the secret or public key, checks the exp claim has not passed, and optionally checks iss (issuer) and aud (audience). No database lookup required.
Access granted (or denied)
If validation passes, the server reads claims from the payload (e.g. role, scope) to make authorization decisions. If expired or tampered, it returns 401 Unauthorized.
Signing Algorithms: HS256 vs RS256 vs ES256
The alg field in the header tells verifiers which algorithm was used. The choice determines who can verify the token and what infrastructure you need.
| Algorithm | Type | Keys | Best for |
|---|---|---|---|
| HS256 | HMAC-SHA256 (symmetric) | One shared secret — same key signs and verifies | Single-service or monolith. Simple setup. Secret must be kept private by all verifiers. |
| RS256 | RSA-SHA256 (asymmetric) | Private key signs; public key verifies (shareable) | Microservices, third-party APIs. Any service can verify without accessing the private key. JWKS endpoint. |
| ES256 | ECDSA P-256 (asymmetric) | Private key signs; public key verifies | Same as RS256 but smaller signatures. Preferred for mobile and IoT where bandwidth matters. |
Recommendation: Use RS256 or ES256 for any multi-service system. The issuer keeps the private key; every API service fetches the public key from a /.well-known/jwks.json endpoint and verifies locally — no round-trip to the auth server per request.
Token Lifetime and the Refresh Token Pattern
Because JWTs are stateless, a stolen token remains valid until it expires. Short expiry limits the damage window — but forces users to re-authenticate frequently. The refresh token pattern solves this tension.
Short-lived: 5–60 minutes
Sent on every API request in the Authorization header.
Stateless — validated by any service with the public key.
Cannot be revoked before expiry without a denylist.
Long-lived: days to weeks
Stored securely (HttpOnly cookie). Never sent to APIs.
Sent only to the auth server to obtain a new access token.
Can be revoked server-side (stored in DB with a revocation flag).
When the access token expires (HTTP 401), the client silently sends the refresh token to /token/refresh. The auth server validates the refresh token, issues a new access token, and optionally rotates the refresh token (returning a new one and invalidating the old). The user never sees a login prompt.
When to Use JWTs
✅ Good fit
- Stateless REST APIs — services validate tokens independently; no shared session store required.
- Microservices — each service verifies the JWT using the issuer's public key via JWKS.
- Mobile and SPA clients — tokens live in memory; refresh tokens stored in secure storage.
- Cross-domain SSO — one identity provider issues a token accepted by multiple services.
- OAuth 2.0 / OIDC — access tokens and ID tokens are typically JWTs in these flows.
❌ Poor fit
- Immediate revocation required — if you must invalidate a token mid-session (e.g., account ban), JWTs require a server-side denylist — at which point you've added statefulness.
- Simple server-rendered apps — server sessions with cookies are simpler, more secure, and easier to revoke.
- High-frequency background jobs — short-lived tokens require frequent refresh; a service account API key is often simpler.
Common JWT Vulnerabilities
Most JWT security failures are not cryptographic — they are implementation mistakes. These three are the most common.
alg: none AttackThe JWT spec defines an alg: none value meaning "unsigned." Some early libraries accepted unsigned tokens if the alg header was set to none — allowing an attacker to forge arbitrary claims with no secret.
Fix: Always explicitly specify the expected algorithm when verifying. Never allow none in production.
If a server uses RS256 and the verifier accepts the algorithm from the token header, an attacker can change the header to alg: HS256 and sign the token with the server's public key (which is not secret). The verifier uses the public key as the HMAC secret — and the forged token passes.
Fix: Hardcode the expected algorithm on the server. Never trust the alg field from the incoming token.
HS256 tokens signed with short or common secrets (like secret, password123, or a UUID) can be brute-forced offline — an attacker extracts the signature and tries candidate keys until one matches. This is faster than it sounds with GPU tooling.
Fix: Use a cryptographically random secret of at least 256 bits (32 bytes). Rotate it if exposed. Or switch to RS256/ES256.
Decode and validate a JWT now
Paste any JWT into the validator to decode the header and payload, check expiry, inspect claims, and verify HMAC signatures — all in your browser.