JSON Web Token
ActiveJSON Web Token (JWT) is a compact, URL-safe method for representing claims between two parties, defined by RFC 7519. A JWT is a Base64URL-encoded JSON header and payload separated by dots, followed by a cryptographic signature. JWTs are self-contained – the recipient can verify them without calling the issuer. They are the standard token format for OAuth 2.0 access tokens and OpenID Connect ID Tokens.
In one line
JWT (RFC 7519, 2015) is a Base64URL-encoded, dot-separated token: header.payload.signature. The header declares the algorithm (alg) and token type. The payload carries claims: registered (iss, sub, aud, exp, iat, jti), public, and private. The signature is produced with a secret (HMAC) or private key (RSA, EC). Recipients verify the signature and validate exp, iss, aud before trusting any claims. JWTs are used as OAuth 2.0 access tokens and OIDC ID Tokens.
Quick Reference
| Field | Size | Description |
|---|---|---|
| Structure | header.payload.sig | Three Base64URL segments separated by dots. header and payload are JSON objects. signature is the HMAC or RSA/EC signature over the encoded header and payload. |
| Header claims | alg, typ, kid | alg: signing algorithm (RS256, HS256, ES256). typ: token type, usually JWT. kid: key ID, used to select the correct verification key from a JWKS. |
| Registered claims | 7 defined | iss (issuer), sub (subject), aud (audience), exp (expiration), nbf (not before), iat (issued at), jti (JWT ID). All optional by RFC but exp, iss, aud are required by OIDC. |
| exp | Unix timestamp | Expiration time. Token MUST be rejected after this time. Allow ≤60 seconds clock skew. Always validate exp. |
| aud | string or array | Intended audience. Recipient MUST verify their identifier is in aud. Prevents a token issued for service A from being used at service B. |
| Signing | HMAC or asymmetric | Symmetric: HS256/HS384/HS512 (shared secret). Asymmetric: RS256/RS384/RS512 (RSA), ES256/ES384/ES512 (ECDSA), PS256/PS384/PS512 (RSASSA-PSS). |
| JWKS | /.well-known/jwks.json | JSON Web Key Set endpoint. Publishes the issuer's public keys for offline JWT verification. Clients cache JWKS and re-fetch on cache miss for unknown kid. |
| alg:none | FORBIDDEN | The none algorithm produces an unsigned JWT. MUST be rejected by all servers. A classic attack: change the alg header to none and strip the signature. |
Key Characteristics
Self-contained
JWTs carry all needed claims. The recipient verifies the signature and reads the payload without calling the issuer. No database lookup required. This is both the main benefit and the main risk – revocation is hard.
Signature verification
Verification requires the issuer's public key (asymmetric) or shared secret (HMAC). Always verify the signature before reading any claims. Accepting an unsigned or incorrectly signed JWT is a critical security flaw.
Not encrypted by default
A JWT (JWS) is signed but not encrypted. The payload is Base64URL-encoded, not encrypted – anyone can decode and read it. Never put passwords, PII, or secrets in a JWT payload unless using JWE (RFC 7516).
Revocation is hard
Self-contained JWTs cannot be revoked without a token denylist. Once issued, a JWT is valid until exp. Keep access token lifetimes short (15–60 minutes). Use refresh tokens with rotation for longer sessions.
Message Format
# JWT structure (decoded)
# Format: Base64URL(header).Base64URL(payload).signature
# Header
{
"alg": "RS256", // signing algorithm
"typ": "JWT", // token type
"kid": "key-2026-01" // key ID for JWKS lookup
}
# Payload (claims)
{
"iss": "https://auth.example.com", // issuer
"sub": "usr_abc123", // subject (user ID)
"aud": "https://api.example.com", // audience
"exp": 1721912400, // expires at (Unix timestamp)
"iat": 1721908800, // issued at
"jti": "f47ac10b-58cc-4372-a567-0e02b2c3d479", // JWT ID (unique)
"email": "[email protected]",
"scope": "read:profile write:orders"
}
# Signature
# RSASSA-PKCS1-v1_5(SHA-256, private_key,
# Base64URL(header) + "." + Base64URL(payload))# Full JWT token (encoded)
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleS0yMDI2LTAxIn0
.eyJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20iLCJzdWIiOiJ1c3JfYWJjMTIzIiwiYXVkIjoiaHR0cHM6Ly9hcGkuZXhhbXBsZS5jb20iLCJleHAiOjE3MjE5MTI0MDAsImlhdCI6MTcyMTkwODgwMH0
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
# Use as Bearer token
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
# Validation checklist (RFC 7519 §7.2 + RFC 8725):
# 1. Verify signature using issuer's public key (from JWKS)
# 2. Verify alg matches expected algorithm (never accept alg:none)
# 3. Verify exp > current time (allow ≤60s clock skew)
# 4. Verify iss matches expected issuer
# 5. Verify aud contains your service's identifier
# 6. Verify nbf ≤ current time (if present)
# 7. Check jti against denylist (if revocation is needed)