Security model: authn vs authz¶
authn is deliberately small, and its shape encodes a few security decisions that are
easy to get wrong when auth is hand-rolled per service. This page explains the why
behind the API so you can use it correctly — and extend it without undermining it.
Authentication is not authorization¶
The package draws a hard line between two questions:
- Authentication (who are you?) — a
Verifierturns a credential into a verifiedIdentity. - Authorization (what may you do?) — an
AuthorizeFuncpredicate turns anIdentity(plus request metadata) into an allow/deny decision.
They map to different wire statuses (401 vs 403), fail for different reasons,
and change on different schedules — conflating them is how "authenticated" quietly
becomes "authorized." authn keeps them as separate types so a call site must make
both decisions explicitly.
authn ships no policy engine. Authorization is your predicate — the built-in
RequireScopes/RequireClaim combinators, or your own function. Keeping policy in the
application, expressed as code, avoids a second configuration language to secure and
audit.
Fail closed, and leak nothing¶
Two rules govern every verifier:
- Fail closed. Any condition that is not a clean success is a failure. An empty mTLS chain, an expired or wrong-audience JWT, an unknown API key, a JWKS that will not fetch — all return an error, none return a partial or "probably fine" identity.
- Leak nothing. A
Verifiermust not encode why authentication failed in a user-facing form. The returned error is server-side detail for your log (redacted); the transport maps every verify error to a single generic401 / Unauthenticated. Telling an attacker whether a key exists, a token expired, or an audience mismatched hands them an oracle.
The ErrUnauthenticated
sentinel exists for your benefit — distinguishing a rejected credential (expected,
log quietly) from an operational failure (a network/config problem, log loudly) — and
never crosses the wire.
JWT-specific hardening¶
The JWT verifier bakes in the mitigations for the classic token-verification pitfalls:
- No
alg: none, no algorithm confusion.AllowedAlgorithmsdefaults to the asymmetric RS/ES 256/384/512 set. The verifier will not accept an unsigned token, and pinning algorithms prevents an attacker swapping an RS256 public key into an HS256 MAC. - Full standard-claim validation.
issmust match;audmust be one of the configured audiences (leaving it empty disables the check and is discouraged);exp/nbfare enforced with a small configurableLeeway(default 60s) for clock skew. - Keys from a trusted JWKS over HTTPS. Signing keys are fetched from the configured
(or OIDC-discovered) JWKS endpoint, which must be HTTPS, and cached with a bounded
RefreshIntervalso key rotation is picked up without refetching per request.
mTLS: the credential is the connection¶
Client-certificate auth does not implement Verifier, because a certificate is a
property of the TLS connection, not a string a caller passes in. It implements
CertVerifier
instead, taking the already-verified chains from the TLS stack
(tls.ConnectionState().VerifiedChains). authn derives an identity from a chain the
platform's TLS layer has already validated against your trust roots — it does not
re-implement certificate-path validation, and it rejects an empty chain.
What stays your responsibility¶
authn verifies credentials and derives identities. It does not:
- terminate TLS or manage certificates (see go/tls);
- extract the credential from the request, or shape the HTTP/gRPC error response — that transport glue is yours;
- store secrets, issue tokens, or run a policy engine.
Doing one thing — turning credentials into verified identities, safely — is what keeps it small enough to review and reuse everywhere.