JWT structure in depth
A JSON Web Token has exactly three parts, separated by dots and Base64url-encoded:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 ← Header
.eyJzdWIiOiIxMjM0NTY3ODkwIn0 ← Payload
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ← Signature
Decoding the first two segments gives JSON objects. The third segment is a cryptographic signature computed over the first two.
Important: Base64url encoding is not encryption. Anyone holding the token can decode the header and payload. Never put secrets (passwords, credit card numbers, access keys) in a JWT payload unless the token is additionally encrypted (JWE, not JWS).
Registered claims
RFC 7519 defines these standard claim names:
| Claim | Meaning | Type |
|---|---|---|
| iss | Issuer — who created the token | String |
| sub | Subject — who the token is about | String |
| aud | Audience — intended recipient | String or array |
| exp | Expiration time | Unix timestamp |
| iat | Issued at | Unix timestamp |
| nbf | Not before — token valid from | Unix timestamp |
| jti | JWT ID — unique token identifier | String |
These are conventionally short to keep token size small. Your application can add arbitrary custom claims alongside them.
Algorithms: symmetric vs asymmetric
HS256 (HMAC-SHA256): a single shared secret is used to both sign and verify. Fast and simple, but both the issuer and verifier must have the same secret. If you share the secret with a third party, that party can also issue tokens — intended for monolithic systems.
RS256 (RSA-SHA256): a private key signs the token; a public key verifies it. The verifier never needs the private key. Ideal for microservices where many services need to verify tokens but only the auth server should issue them.
ES256 (ECDSA-SHA256): like RS256 but uses Elliptic Curve Cryptography. Produces much shorter signatures than RSA and is faster to verify.
alg: none: the "none" algorithm means the token has no signature. Never accept tokens with alg: none unless you explicitly expect unsigned tokens — some early JWT libraries had a vulnerability where an attacker could strip the signature and set alg: none to forge arbitrary tokens.
Expiry and refresh patterns
Every JWT should have an exp claim. A reasonable access token lifetime is 15 minutes to 1 hour. Short-lived tokens limit the damage if a token is stolen.
Long sessions use refresh tokens: a longer-lived opaque token stored securely (HttpOnly cookie) that can be exchanged for a new short-lived access token. The refresh token can be revoked server-side; the access token cannot (until it expires).
JWTs are not sessions
A common misconception: JWTs can replace server-side sessions. They cannot be revoked before expiry (without a server-side allowlist/denylist that defeats the stateless advantage). They are not encrypted by default. They grow larger with each added claim.
Use JWTs for short-lived authentication tokens in distributed systems. Use server-side sessions for long-lived user sessions in traditional web apps where you can control session invalidation on logout.