backend / authentication / jwt / 01_jwt_basics.md

JWT Basics — Structure and Encoding

6 interview angles 5 min read source

JWT Basics — Structure and Encoding

A JWT is three base64url-encoded segments joined by dots: header.payload.signature. Each segment is a JSON object (the signature is binary). Compact, URL-safe, self-describing — but not encrypted.

The anatomy

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJuYW1lIjoiQWxpY2UiLCJleHAiOjE3MTYzMjAwMDB9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
└──────── header ──────────┘ └────────────── payload ──────────────┘ └────────── signature ──────────┘

Decode the first two segments at jwt.io — they’re plain JSON. The payload is not secret. Anyone with the token reads everything inside.

{ "alg": "HS256", "typ": "JWT", "kid": "abc123" }
Field Means
alg signing algorithm (HS256, RS256, ES256, etc. — see 02_signing_algorithms.md)
typ typically “JWT”
kid key ID — which key in your JWKS signed it (for rotation)
cty content type, rare

Payload (claims)

{
  "sub": "user_42",
  "name": "Alice",
  "iat": 1716316400,
  "exp": 1716320000,
  "aud": "myapp",
  "iss": "https://auth.example.com"
}

Whatever JSON object you want. Standard claims (registered: sub, iat, exp, aud, iss, nbf, jti) plus your own. See 03_claims.md.

Signature

HMACSHA256(
  base64url(header) + "." + base64url(payload),
  secret
)

For HS256 (symmetric). For RS256 (asymmetric), it’s RSA-PKCS1-v1_5 with SHA-256 using the private key; verified with the public key.

Critical: the signature signs header + "." + payload. It guarantees:

  • Whoever signed had the secret/private key.
  • Neither header nor payload was modified after signing.

It does NOT guarantee:

  • The content is private (it’s base64-encoded, not encrypted).
  • The token hasn’t been stolen.
  • The user is still allowed to do whatever (auth state may have changed since issuance).

Base64url — not regular base64

Regular base64: + / =
Base64url:      - _ (no padding)

URL-safe variant: + and / (which conflict with URLs) become - and _. No padding = characters (they’re optional). Same data, friendlier encoding.

A complete example in Python

import jwt
import datetime

# Encode
secret = "supersecretkey"
token = jwt.encode(
    {
        "sub": "user_42",
        "name": "Alice",
        "iat": datetime.datetime.now(datetime.timezone.utc),
        "exp": datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(hours=1),
    },
    secret,
    algorithm="HS256",
)
print(token)
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

# Decode
payload = jwt.decode(token, secret, algorithms=["HS256"])
print(payload)
# {'sub': 'user_42', 'name': 'Alice', 'iat': ..., 'exp': ...}

Always pass algorithms=[...] — never rely on the token’s own alg field. See 02_signing_algorithms.md.

How JWT is used (typical flow)

1. Client logs in:        POST /login {user, pass}
2. Server validates:      DB lookup, password check
3. Server issues JWT:     signs {sub, exp, ...} → returns token
4. Client stores token:   cookie / memory / secure storage
5. Client sends token:    Authorization: Bearer <token> on every request
6. Server verifies:       check signature, check exp, extract sub → know who the user is
7. Server authorizes:     decide if sub can do the requested operation

Step 6 is local — no DB lookup needed to validate the signature. That’s the JWT pitch: stateless servers, horizontal scaling.

See 07_jwt_vs_session.md for the comparison.

Where the token lives

Storage Pros Cons
localStorage (browser JS) simple XSS reads it — leaks the session
sessionStorage tab-scoped same XSS risk
HttpOnly cookie JS can’t access (XSS-safe) needs CSRF defense
In-memory (SPA) XSS-safe (lost on reload) re-auth on every reload
Mobile secure storage (Keychain/Keystore) best for native only mobile

Modern web app recommendation: refresh token in HttpOnly Secure cookie, access token in memory. See 04_access_refresh_tokens.md.

What JWT does well

  • Stateless backends: any replica can verify without a DB lookup.
  • Microservices: pass the JWT downstream; each service verifies independently.
  • Federated identity: SSO (OIDC ID tokens are JWTs).
  • Self-contained claims: roles / tenant / scopes travel with the token.

What JWT doesn’t do well

  • Revocation: stateless = can’t easily kick someone out before exp. Workarounds (blocklist, short tokens) lose statelessness.
  • Privacy: claims are readable. PII leaks.
  • Long-lived sessions: long-expiry JWT = long exposure window after theft.
  • Mid-session permission changes: token still has old roles until expiry.

For these cases, server-side sessions (opaque session ID + Redis) often beat JWT. See 07_jwt_vs_session.md.

Common pitfalls (preview)

  • alg: none attack — token claims no signature; library accepts.
  • Algorithm confusion (RS256 ↔ HS256).
  • Missing aud / iss validation.
  • PII in payload.
  • No expiry (exp) or excessive lifetime.
  • Storing in localStorage with cookie auth.

Full coverage: ../../25_security/06_jwt_pitfalls.md.

Common interview confusions

  • “JWTs are encrypted.” — signed, not encrypted. Anyone can read the payload. Use JWE if you need encryption. See 08_jose_family.md.
  • “JWTs are inherently more secure than sessions.” — they’re stateless, which is a different property. Sessions with Redis can be equally secure and easier to revoke.
  • “JWTs solve all auth problems.” — they solve stateless auth specifically. They make revocation, mid-session updates, and privacy harder.

Interview angle

  • “What is a JWT?” — JSON Web Token: three base64url segments (header.payload.signature). Header declares the signing algorithm. Payload contains claims (user ID, expiry, etc.). Signature proves the token wasn’t tampered with. Self-contained — verifying servers don’t need to look up state.
  • “What does each part contain?” — header: alg and typ. Payload: claims (registered like sub, exp, iss, aud; custom). Signature: HMAC or RSA/ECDSA over the encoded header + payload.
  • “Is a JWT encrypted?” — no, it’s signed. Anyone with the token reads the payload. For encryption, use JWE (rarely seen).
  • “What’s the typical JWT flow?” — client logs in → server issues signed JWT → client sends Authorization: Bearer <jwt> on every request → server verifies signature + claims → extracts user identity → authorizes.
  • “Where should the client store the JWT?” — for web: refresh token in HttpOnly Secure cookie, access token in memory. localStorage is XSS-vulnerable. For mobile: Keychain / Keystore.
  • “Why is JWT compact and URL-safe?” — base64url encoding (no +, /, =); fits in URL params, headers, cookies. Small enough to ride along on every request without bloat.