Repository navigation
JWT
github-actions[bot] edited this page Oct 31, 2026
·
5 revisions
RS512 tokens that carry an auth.Identity from a signer (the forward-auth image, or your own jwt.Signer) to backends. A backend verifies them with jwt.Verifier against the signer's JWKS and gets the identity without sessions.
Tested (jwt/example_test.go, ExampleVerifier):
verifier, err := jwt.NewVerifier(context.Background(), &jwt.VerifierOptions{JWKSURL: forwardAuth.URL, Issuer: "https://auth.example.com", Audience: "builds-api"})
if err != nil {
fmt.Println(err)
return
}
api := verifier.Middleware()(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
identity := auth.IdentityFrom(request.Context())-
JWKSURL: the signer's JWKS overhttps, e.g.https://app.example.com/auth/jwks(the image serves it through Traefik); plainhttponly on a loopback host, no user info. The keys decide which tokens pass, so a JWKS fetched over plainhttp(also in-cluster,http://forwardauth:8080/auth/jwks) is refused unlessInsecureJWKSURL: truesays you trust the network path to it: anyone on the way could swap the keys.NewVerifierfetches it once and fails fast. -
Issuer: the signer's issuer; for the imageJWT_ISSUER, default itsEXTERNAL_URL. -
Audience(required): this backend's name, one of the signer'saudvalues (the image'sJWT_AUDIENCE). A token without it is refused, so a token minted for another upstream of the same signer does not pass here. -
Middleware()readsAuthorization: Bearer <token>(HeaderNameto change; scheme in any case, spaces around the token ignored) and answers 401unauthenticated(missing) orinvalid_token(invalid), withWWW-Authenticate: Bearer. Why a token was refused (expired, issuer mismatch, ...) is in the outcome's Reason, never token content.Verify(ctx, token)is the same check as a function. -
Verifychecks the signature (with the JWKS key the token'skidnames, under that key's algorithm),iss,aud,exp(required),nbfandiat(30 s skew).subbecomesSubject,preferred_usernameUsername;provider,user_id,name,email,picture,roles,realm_roles,client_roles,groups,teamsfill the matching fields,permissionsfillsPermissions(entries that do not parse are dropped). - On gin:
ginadapter.Middleware(verifier.Middleware()), thenginadapter.Require(...). - The identity comes back
Active, withRoles,OIDCRoles,OIDCClientRoles,Groups,Teams,Permissionsand every claim inClaims; anactclaim (RFC 8693, written while impersonating) becomes an activeImpersonatorwith onlySubject,ProviderandUserID, soauthorization.Real(...)sees none of its roles;AttributesandDataare not carried. Claim table:go doc github.com/govengers/auth/jwt(jwt/doc.go).
jwt.NewSigner(&jwt.SignerOptions{PrivateKey, Issuer, Audience, Lifetime}) with a PEM RSA key of at least 2048 bits (no generated fallback: every replica must load the same key). Sign(identity) mints a token (default lifetime 5 min, key ID = RFC 7638 thumbprint); JWKSHandler() serves the public key. Tested: ExampleSigner in jwt/example_test.go.
-
Signrefuses an inactive identity (403): a verified token always yields an active one. - Do not mount
apikeymiddleware in front of the verifier: both readAuthorization: Bearer, andapikeyrefuses foreign bearer tokens with 401. - JWKS refetch: after
RefreshInterval(default 1 h, at least 1 min) or for an unknown key ID, at most once a minute, detached from the request that triggered it and bounded by 10 s. While it runs, tokens with a known key ID verify with the current keys; tokens with an unknown one wait for it, at most until their own request ends. A failed refetch keeps the old keys; a token with an unknown key ID then gets 500. No token cache, no goroutine. -
MaximumKeyAge(default 24 h, at leastRefreshInterval): keys are trusted that long after the last successful fetch. While the JWKS stays unreachable beyond it, every token gets 500 (a plain error fromVerify) until a fetch succeeds, so a key the signer rotated out is not trusted forever. - Only RSA keys with an RSA signature
alg(RS256/384/512, PS256/384/512), akidand usesig(or none) count; other keys and entries that do not parse are ignored. A JWKS without a usable key, that is not a JSON JWK set or larger than 1 MiB failsNewVerifier. Errors name the JWKS URL without credentials and query and never repeat the answer's bytes. - Trust the token, never identity headers a client could set; see the Traefik settings.
Source: jwt/ (signer.go, verifier.go, claims.go).
Mirror of docs/wiki at v1.3.0 (486bfa3). Edit the repository, not this wiki.