JWT — Stateless Identity Tokens
Self-contained token:
header.payload.signature— verifiable without server state lookup.
When to use
- Stateless API auth across services
- Short-lived tokens (minutes, not days)
Tradeoffs
- Cannot revoke before expiry without a token blocklist (defeats statelessness)
- Payload is base64-encoded, not encrypted — anyone can read claims
A JWT has three dot-separated parts, each base64url-encoded:
| Part | Content | Example fields |
|---|---|---|
| Header | Algorithm + type | {"alg":"RS256","typ":"JWT"} |
| Payload | Claims | {"sub":"u123","exp":1700000000,"roles":["admin"]} |
| Signature | HMAC or RSA over header.payload | Prevents tampering |
- Go
- Python
import "github.com/golang-jwt/jwt/v5"
func validateToken(tokenStr, pubKeyPEM string) (*Claims, error) {
pubKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(pubKeyPEM))
if err != nil {
return nil, err
}
token, err := jwt.ParseWithClaims(
tokenStr, &Claims{},
func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("unexpected alg: %v", t.Header["alg"])
}
return pubKey, nil
},
)
if err != nil || !token.Valid {
return nil, fmt.Errorf("invalid token: %w", err)
}
return token.Claims.(*Claims), nil
}
import jwt # PyJWT
def validate_token(token_str: str, public_key: str) -> dict:
try:
claims = jwt.decode(
token_str,
public_key,
algorithms=["RS256"], # explicit — never omit
options={"require": ["exp", "sub", "iat"]},
)
except jwt.ExpiredSignatureError:
raise AuthError("token expired")
except jwt.InvalidTokenError as e:
raise AuthError(f"invalid token: {e}")
return claims
Gotcha: JWT payload is base64-encoded — not encrypted. Never store secrets in it. Use JWE (JSON Web Encryption) for encrypted tokens. Always specify the expected algorithm on parse —
alg: noneattacks are real.