jwt
JSON Web Tokens (JWT) are small, signed messages that carry claims (facts) about a user or a system. They let servers trust information without storing session state.
What is a JWT?
- A compact string that looks like:
xxxxx.yyyyy.zzzzz - It has three parts:
- Header: type and signing algorithm (e.g., HS256, RS256)
- Payload: claims like
sub(user id),exp(expires at),role - Signature: proves the token was issued by a trusted party and not changed
Example (conceptual):
Header { "alg": "RS256", "typ": "JWT" }
Payload { "sub": "123", "name": "Rami", "role": "admin", "exp": 1735756800 }
Signature sign(base64url(header) + "." + base64url(payload), privateKey)
Why use JWTs?
- Stateless auth: server doesn’t keep per-user session in memory/DB
- Portable: works across services/APIs and clouds
- Self-contained: includes the info (claims) APIs need to authorize
- Standard: widely supported in browsers, mobile, and backends
How they’re used (typical flow)
- Issue: An auth server checks credentials, then creates a JWT with claims and an expiry (
exp), and signs it. - Send: The token is returned to the client (often in an HTTP-only cookie or response body).
- Use: The client sends it on each request, usually in the
Authorizationheader asBearer <token>. - Verify: The API verifies the signature and expiry. If valid, it trusts the claims and serves the request.
Two practical examples
1) User login to call an API
- User logs in with email/password.
- Auth service returns a JWT with:
sub=userId,role=user,exp=<short TTL>. - Browser stores it in an HTTP-only, Secure cookie (or memory).
- Subsequent API calls include the token via cookie or
Authorization: Bearer .... - API verifies signature and
exp, reads claims (e.g., role) to allow/deny.
Why it helps: No server-side session store. Multiple API instances can validate the same token if they share the signing public key.
2) Service-to-service (machine) calls
- A backend job or microservice fetches a JWT from an auth server using its client credentials.
- It calls another internal API with
Authorization: Bearer <token>. - The receiving service verifies the token with the issuer’s public key and authorizes by scopes/claims (e.g.,
scope: payments:write).
Why it helps: Secure, verifiable identity between services without sharing passwords.
Good practices
- Keep tokens short-lived (
expminutes, not days). Refresh with a separate refresh token if needed. - Never put secrets or PII in the payload (it’s only base64url-encoded, not encrypted).
- Prefer asymmetric signing (RS256/ES256): issuer signs with private key; services verify with public key.
- Pin audience (
aud) and issuer (iss) and check them on every request. - Plan for logout/revocation: short TTL, rotate keys, and optionally keep a denylist for critical cases.
Common gotchas
- “Signed” ≠ encrypted. Anyone who sees the token can read its payload.
- Clock skew: allow a small leeway when validating
nbf/exp. - Avoid algorithm confusion: don’t accept
alg=none; lock your verifier to expected algorithms.