JWT Guide
How JSON Web Tokens work: three-part structure, signing algorithms, standard claims, validation steps, and security pitfalls to avoid.
What is a JWT?
A JSON Web Token (JWT) is a compact, URL-safe token used to represent claims between two parties. It is most commonly used for stateless authentication: the server issues a signed token at login; subsequent requests carry the token, and the server verifies the signature without consulting a database.
A JWT looks like: xxxxx.yyyyy.zzzzz — three Base64URL-encoded sections separated by dots: Header, Payload, Signature.
JWT Structure
Each part is independently Base64URL-encoded (no padding). The three parts are joined with dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphbmUgRG9lIiwiaWF0IjoxNzE2MjM5MDIyLCJleHAiOjE3MTYyNDI2MjJ9
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c Header
{"alg": "HS256", "typ": "JWT"} alg — the signing algorithm. typ — always JWT.
Payload (Example)
{
"sub": "1234567890",
"name": "Jane Doe",
"role": "admin",
"iat": 1716239022,
"exp": 1716242622
} The payload is not encrypted — anyone can decode it. Never put passwords, credit card numbers, or other secrets here.
Registered Claims
| Claim | Name | Type | Description |
|---|---|---|---|
iss | Issuer | string | Who issued the token (e.g., https://auth.example.com) |
sub | Subject | string | The entity the token is about, usually a user ID |
aud | Audience | string | string[] | Intended recipient(s) of the token |
exp | Expiration Time | NumericDate | Unix timestamp after which the token must be rejected |
nbf | Not Before | NumericDate | Token must not be accepted before this Unix timestamp |
iat | Issued At | NumericDate | Unix timestamp when the token was issued |
jti | JWT ID | string | Unique identifier; useful for preventing replay attacks via a denylist |
Signing Algorithms
The signature covers Base64URL(header) + "." + Base64URL(payload). If any bit changes, signature verification fails.
| Algorithm | Family | Key Type | Notes |
|---|---|---|---|
HS256 | HMAC | Shared secret | Fast; both sides need the secret — not suitable for public clients |
HS384 | HMAC | Shared secret | Stronger hash; same key management constraints as HS256 |
HS512 | HMAC | Shared secret | Strongest HMAC variant |
RS256 | RSA | RSA key pair | Signs with private key; verifies with public key. Best for public APIs / OIDC |
RS384 | RSA | RSA key pair | Same as RS256 with SHA-384 hash |
RS512 | RSA | RSA key pair | Same as RS256 with SHA-512 hash |
ES256 | ECDSA | EC key pair (P-256) | Smaller signatures than RSA; recommended for modern systems |
PS256 | RSASSA-PSS | RSA key pair | RSA with probabilistic padding; FIPS-approved alternative to RS256 |
Validation Steps
- Verify the signature using the correct key and the algorithm declared in the header.
- Reject
alg: none— never accept unsigned tokens unless explicitly designed for it. - Check
exp: reject if current time ≥ exp (allow a few seconds of clock skew). - Check
nbf(if present): reject if current time < nbf. - Check
iss: verify the issuer matches your expected value. - Check
aud: verify your service is in the audience list.
Security Best Practices
- Never store sensitive data in the payload — it is only encoded, not encrypted.
- Use short expiration times — 15 minutes for access tokens; use refresh tokens for longer sessions.
- Always validate the algorithm — pin the expected algorithm server-side; don't trust the header's
algfield alone. - Prefer RS256/ES256 for public-facing APIs — the private signing key never leaves your auth server.
- Store tokens securely — httpOnly cookies for web apps (prevents XSS theft); avoid localStorage for sensitive tokens.
- Use JWTs for stateless APIs, not sessions requiring revocation — see below.
When to Use JWT (and When Not To)
Good Fit
- Stateless API authentication
- Microservices (service-to-service auth)
- Single Sign-On (OIDC id_token)
- Short-lived one-time tokens (email verification)
Poor Fit
- Sessions requiring instant revocation (logout before exp)
- Storing large amounts of claims (tokens get big)
- Replacing cookies entirely in browser apps without care
Inspect and decode JWT tokens visually with the JWT Decoder tool.
Frequently Asked Questions
Is a JWT encrypted?
No — a standard JWT (JWS) is only signed, not encrypted. The header and payload are Base64URL-encoded, which anyone can decode in seconds. Signing ensures the data hasn't been tampered with, but doesn't hide it. If you need to hide the payload, use a JWE (JSON Web Encryption) token, or simply don't put sensitive data in the JWT.
What is the difference between HS256 and RS256?
HS256 uses a shared secret — the same key both signs and verifies. This means every service that needs to verify tokens must hold the secret key, creating a security risk. RS256 uses an asymmetric RSA key pair: the auth server signs with the private key, and any service can verify with the public key (safely shareable). RS256 (or ES256) is preferred for public APIs and multi-service architectures.
How do I invalidate a JWT?
JWTs are stateless — you can't truly "delete" one. Options: (1) Short expiration: use a 15-minute access token so compromise windows are small. (2) Token denylist: store invalidated jti values in Redis until their natural expiry. (3) Refresh token rotation: keep short-lived access tokens and server-side revocable refresh tokens. For immediate revocation at scale, option 2 or 3 is required.