- Docs
- Security
- Authentication
Authentication
Issue and verify caller identity with JWT, session, or OIDC behind one interface.
auth answers who is calling, never what they may do. It covers jwt (auth/jwt), session (auth/session), and oidc (auth/oidc); see Password hashing and Authorization.
Quick start
Section titled “Quick start”Set a 32+ byte JWT secret, then resolve the service; Open refuses shorter secrets and config.Default() ships none.
AUTH_ADAPTER=jwtAUTH_JWT_SECRET=change-me-to-at-least-32-bytesAUTH_ADAPTER=jwtAUTH_JWT_SECRET=prod-secret-at-least-32-bytes-longc := container.New(cfg)a, err := c.Auth()if err != nil { return err // secret too short, or adapter unavailable}AUTH_JWT_SECRET maps to options.jwt.secret (env splits at the first underscore). Keys: jwt.issuer, jwt.audience, jwt.max_ttl, jwt.leeway, jwt.keys, jwt.active_kid, oidc.issuer, oidc.client_id, oidc.timeout, oidc.allow_insecure. See the auth config reference.
Key rotation
Section titled “Key rotation”List every accepted key with a kid and point active_kid at the signer. New tokens carry the kid header; verification selects the key by kid and still accepts kid-less tokens signed with the legacy secret, so old clients keep working through the overlap. Unknown kids fail closed. Keep a retired key listed until every token signed with it has expired, then remove it. HS256 only; asymmetric algorithms (EdDSA/RS256) are not supported — use a custom auth adapter if you need them. alg: none tokens are rejected.
AUTH_ADAPTER=jwtAUTH_JWT_SECRET=previous-secret-at-least-32-bytes-longAUTH_JWT_KEYS_- KID=2026-09AUTH_JWT_KEYS_SECRET=previous-secret-at-least-32-bytes-longAUTH_JWT_ACTIVE_KID=2026-10AUTH_JWT_LEEWAY=30sTokens carrying a future nbf (not-before) claim are rejected; exp is always required. Both honor leeway, which defaults to 0 (strict).
Log in and call with a bearer token
Section titled “Log in and call with a bearer token”Log in, then send the token on every other route:
curl -s -X POST localhost:8080/api/login \ -H 'Content-Type: application/json' \ -d '{"email":"you@example.com","password":"correct horse battery staple"}'curl -s localhost:8080/api/spaces -H "Authorization: Bearer $TOKEN"Login returns 200 with a signed JWT in token; register 201; missing or invalid tokens 401. Issue mints, Verify checks per request, Revoke invalidates a token (jwt keeps revoked jtis in a per-process store; session uses its store). Verify returns distinct ErrInvalidToken and ErrTokenExpired errors; map both to one generic 401 so responses leak nothing. Log details server-side, never token values.
Production checklist
Section titled “Production checklist”- CSPRNG secret (32+ bytes) in env or a mounted file, never source. See Secrets.
- Rotate with overlap: add the new key to
jwt.keys, flipjwt.active_kid, and only remove the old key after its tokens have expired. - Prefer
sessionfor immediate revocation across instances; JWT revocation is per-process by default (the memory store dies with the process and is invisible to other instances), so multi-instance deployments must inject the sharedrevocation/redisstore viaauth.JWTOptions.RevocationStore— otherwise revoked tokens stay valid on sibling instances until TTL. - Rate-limit login; hashing cost is not an attempt limit. See Rate limiting.
- Display config only via
cfg.RedactedServices()orconfig.Redact.
Troubleshooting
Section titled “Troubleshooting”Open rejects the JWT secret
Shorter than 32 bytes (MinSecretLen). Set a longer AUTH_JWT_SECRET.
All logins return 401 after rotation
Old-secret tokens no longer verify: either the retired kid was removed from jwt.keys before its tokens expired, or active_kid points at a key ID with no matching entry. Restore the old key (or fix active_kid), or log in again for a fresh token.
The session adapter fails at startup
It needs an injected session.Store (Go-only, not config). Wire a shared store for multiple instances.
OIDC login loops
The provider redirect URL and the callback disagree. Align the app registration with the deployed callback URL.