What authn does not do¶
authn is a small package with a deliberately narrow job: turn a credential into a
verified identity, and let you decide what that identity may do. Almost everything else
people associate with "auth" is out of scope.
This page lists what is absent, so you can find out here rather than halfway through an integration. Some of it is a design decision, some of it is simply unbuilt; where the distinction matters, it is stated.
Credential types it cannot verify¶
| Credential | Status |
|---|---|
| API key / shared secret | Supported |
| JWT signed with an asymmetric key published in a JWKS | Supported |
| Client certificate (mTLS) | Supported |
| JWT signed with a shared secret (HS256 and friends) | Not supported, and refused deliberately |
| Opaque tokens requiring introspection (RFC 7662) | Not supported |
| HTTP Basic authentication | Not supported |
| Session cookies | Not supported |
| Signed-request schemes (AWS SigV4 and similar) | Not supported |
Symmetric-key JWTs are refused, not merely unimplemented¶
NewJWTVerifier requires a JWKS URL, and putting any HS* algorithm in
AllowedAlgorithms fails construction outright:
A JWKS publishes public keys. If HMAC verification were also enabled, an attacker
could take that public key, use it as the HMAC secret, sign a token of their choosing
with alg: HS256, and have it verify. That is the classic algorithm-confusion attack,
and the only reliable defence is to make the two modes mutually exclusive. There is no
flag to override it.
If your issuer signs with a shared secret, authn cannot verify its tokens. Move the
issuer to asymmetric signing, or verify those tokens yourself with golang-jwt
directly.
Key material it cannot use¶
- A public key you already have. There is no constructor that takes a
crypto.PublicKey, a PEM file, or a static key set. Keys come from an HTTPS JWKS endpoint and nowhere else. x5c/x5tJWKS entries. Only the raw key parameters are read —nandefor RSA,crv,xandyfor EC. A JWKS that publishes certificates instead of raw key parameters yields no usable keys.- Ed25519 (
OKP) keys, even though the underlying JWT library can verify EdDSA signatures. OnlyRSAandECkey types are parsed, and for EC onlyP-256,P-384andP-521. - RSA-PSS by default.
PS256/PS384/PS512verify correctly but are not in the defaultAllowedAlgorithms, so you must list them explicitly. - A plaintext JWKS endpoint. HTTPS is required with no exemption, including for
localhostand test servers. Testing against a local issuer means anhttptest.NewTLSServerand passing its client viaJWTConfig.HTTPClient.
OIDC: discovery only¶
WithOIDCDiscovery reads one thing out of /.well-known/openid-configuration — the
jwks_uri — and nothing else. authn is a resource server, not a client.
There is no authorization-code flow, no PKCE, no token endpoint, no refresh-token
handling, no userinfo call, no logout, and no nonce or at_hash validation. If you
need a user to log in, that is a different library.
Token lifecycle it does not track¶
- No revocation. A token is valid until its
exp. There is no introspection call, nojtidenylist and no way to invalidate a token early. - No maximum token age. The
iatclaim is not validated — not required, and not checked for being implausible. A token withiattwo days in the future verifies fine. subis not required. A token without one authenticates successfully with an emptyIdentity.Subject.- One issuer per verifier.
JWTConfig.Issueris a single string andJWKSURLa single endpoint. Accepting tokens from two issuers means two verifiers and your own logic for choosing between them.
Authorization: a predicate, and two combinators¶
The whole authorization surface is AuthorizeFunc — func(ctx, *Identity) bool — plus
RequireScopes and RequireClaim.
There is no RBAC engine, no ABAC, no policy language, no rule file and no decision cache. That is a design decision rather than a gap: policy expressed as configuration becomes a second language to secure, review and audit, and every non-trivial deployment outgrows whatever model the library picked. The predicate is shaped as a hole you fill — evaluate a role map, call an external authorization service, whatever you need.
What that means in practice is that the combinators are thin:
- There is no
any-of,orornotcombinator.RequireScopesis all-of only. - There is no "claim contains" test.
RequireClaimis exact deep equality, so an array-valued claim likegroupsneeds a hand-written predicate rather than a combinator. RequireClaimalways denies API-key and mTLS identities, because both havenilclaims.
Transport work it leaves to you¶
authn has no HTTP or gRPC server imports, which is what makes it usable from both. The
consequence is that everything touching a request is yours:
- extracting the credential from an
Authorizationheader, a metadata entry or a query parameter; - deciding which verifier applies to a given request;
- turning a verify error into a
401and afalsepredicate into a403; - shaping the response body, and adding a
WWW-Authenticateheader if you want one; - terminating TLS, loading certificates and configuring a client CA pool — see go/tls.
The getting-started tutorial writes that glue in about twenty lines, and it is the same twenty lines every time.
There is also no helper for trying several verifiers in turn. Accepting both an API
key and a JWT on the same endpoint is a switch you write.
API-key handling it does not provide¶
The API-key verifier compares keys in constant time and does nothing else. It does not:
- generate, store, hash at rest, or rotate keys;
- expire a key, or attach scopes or permissions to one;
- rate-limit, lock out, or otherwise throttle repeated failures;
- record an audit trail of use.
The key set is also fixed at construction. There is no method to add or remove an entry, so rotating a key means building a new verifier and swapping it in.
Two things it deliberately does not reject, which are easy to assume it would:
duplicate keys across entries — the last matching entry wins — and an entry with an
empty Subject.
Observability it does not emit¶
There is no logger, no metrics, no tracing and no callback hooks. Nothing reports a
JWKS refresh succeeding or failing, a cache hit, or a rejected credential. Everything
you want to see has to be recorded by the caller from what Verify returns.
This keeps the dependency footprint at two modules, which a guard test enforces — but it does mean a JWKS endpoint that has been failing for an hour is invisible unless you are logging the operational error branch. See Distinguishing a bad credential from a broken dependency.
Tuning it does not expose¶
Three values are package constants with no configuration path: the 30-second floor between JWKS fetch attempts, the 10-second fetch timeout, and the 1 MiB / 64-key caps on a JWKS document. Defaults and hard limits lists them with their failure modes.
Behaviour that surprises people, but is not a limitation¶
Two things get reported as bugs and are neither:
- A rotated signing key is rejected for up to
RefreshInterval. An unknownkiddoes not force a JWKS fetch. Why a rotated signing key can be rejected for up to fifteen minutes explains the reasoning. - Every authentication failure produces the same opaque error. Telling a caller which part of their credential was wrong is an oracle. The detail is in the wrapped error for your log; see the security model.