Skip to main content

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:

PartContentExample fields
HeaderAlgorithm + type{"alg":"RS256","typ":"JWT"}
PayloadClaims{"sub":"u123","exp":1700000000,"roles":["admin"]}
SignatureHMAC or RSA over header.payloadPrevents tampering
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
}

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: none attacks are real.