A JWT (JSON Web Token, RFC 7519) is a compact, URL-safe, self-contained token that carries a set of claims — statements about a user or session — signed by an issuer. It’s the workhorse of modern API authentication and OAuth 2.0.
Read this if you’re building an API, integrating with an OAuth provider, or debugging why your production login endpoint returns 401.
Structure — Three Segments
Every JWT has three base64url-encoded segments joined with dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMifQ.SflKxwRJSMeKKF2QT4fw...
↑ header ↑ payload ↑ signature
Decode segments 1 and 2 with any base64 decoder — no key needed.
Header
{ "alg": "HS256", "typ": "JWT" }
alg— signing algorithm (HS256,RS256,ES256,EdDSA)typ— alwaysJWTfor JWTskid— optional key ID pointing to the JWK that verifies the signature
Payload — the Claims
{
"sub": "user-42",
"iss": "https://auth.example.com",
"aud": "https://api.example.com",
"exp": 1735689600,
"iat": 1735686000,
"role": "admin"
}
Standard claims (RFC 7519):
| Claim | Purpose |
|---|---|
iss | Issuer identifier |
sub | Subject (user ID) |
aud | Intended audience |
exp | Expiration timestamp (Unix seconds) |
iat | Issued at |
nbf | Not valid before |
jti | Unique token ID (for revocation lists) |
Custom claims (role, tenant_id, permissions) go alongside — but keep them minimal because every byte gets sent on every request.
Signature
The signature covers {base64(header)}.{base64(payload)} — protecting them from tampering:
- HS256 —
HMAC(secret, header + '.' + payload)— symmetric, shared secret - RS256 — RSA signature over SHA-256 hash — asymmetric, private key signs, public key verifies (via a JWK)
- ES256 — ECDSA over the P-256 curve — asymmetric, smaller signatures than RS256
- EdDSA — Edwards-curve signature (Ed25519) — asymmetric, fastest verification
Anyone can read the payload; only holders of the key can forge a valid signature.
Common Vulnerabilities
alg: none— old libraries accepted this. Always allowlist expected algorithms server-side.- Symmetric-key confusion — sending a public RSA key to a lib that expected HMAC. Result: the public key is the “secret” and the whole world can forge tokens.
- Missing
expvalidation — token remains valid forever. - Trusting
algfrom the token header — attacker picks the algorithm. Server should pin the expected algorithm. - Storing secrets in the payload — remember: JWS payload is public.
JWT vs Session Cookies
| JWT | Session Cookie | |
|---|---|---|
| State | Stateless (server holds nothing) | Server-side session store |
| Revocation | Hard (see below) | Delete session record |
| Size | Larger (base64-encoded JSON) | Just an opaque ID |
| Cross-domain | Easy (Authorization header) | Cookie scope rules |
| Common use | APIs, OAuth, microservices | Web apps with server rendering |
Revocation Strategies
- Short expiry (5-15 min) + refresh token — most common. Access token expires quickly; a longer-lived refresh token stored server-side gets new access tokens.
- JTI revocation list — every token has a
jti; check against a fast KV store on verify. - Session-tied JWT — verify against a server-side session record.
Tools
- Paste any JWT into the JWT decoder to inspect header, payload, and signature format.
- Generate signed JWTs for testing with the JWT generator.
- Understand the underlying hashes with the SHA-256 and SHA-512 glossary entries.
Related
Check the JWT glossary entry, the JWS entry, and the JWK entry for signature-verification key discovery.
Common Questions
Is a JWT encrypted or just signed?
Standard JWTs (JWS) are signed, not encrypted. The header and payload are base64url-encoded, which is not encryption — anyone can decode them and read the claims. If you need the payload confidential, you must use JWE (JSON Web Encryption), which encrypts the payload. In practice, most systems use JWS and treat the payload as public metadata.
Should I store a JWT in localStorage or a cookie?
For browser applications, prefer a Secure, HttpOnly, SameSite=Strict cookie. localStorage is accessible to any JavaScript on the page, so a single XSS bug leaks every user's token. A properly-scoped cookie is protected from JavaScript reads. Downsides: cookies require CSRF protection (SameSite handles most cases) and are attached to every request to the origin.
What is the difference between HS256 and RS256?
HS256 is HMAC with SHA-256 — a symmetric algorithm where the same secret both signs and verifies tokens. Anyone holding the secret can forge tokens, so it's only appropriate when the signer and verifier are the same trust boundary. RS256 is RSA signature with SHA-256 — asymmetric. The signer uses a private key; verifiers use the public key. RS256 is the right choice for OAuth issuers whose tokens are verified by many downstream services.
Can I revoke a JWT before it expires?
Not directly — JWTs are stateless by design. Options: (1) short expiry (5-15 min) with refresh tokens, (2) a revocation list checked at verification time, (3) session-tied JWT with a jti (JWT ID) claim recorded in a fast KV store. The right choice depends on how quickly a compromised token must stop working.
What is the 'alg: none' JWT vulnerability?
JWT libraries historically accepted 'alg: none' as a valid signing algorithm — meaning no signature was required at all. Attackers could strip the signature, set alg=none in the header, and forge any claims. Modern libraries reject it by default. Always allowlist the expected algorithm when verifying — do not trust the alg field from the token.