Repository navigation
Releases: govengers/auth
Release list
v1.3.0
New
session.Guard.Identify(ctx, request): the middleware's session lookup without a response writer, for long-lived connections such as a WebSocket re-validating the cookies of its upgrade request. It ends an expired impersonation, runs a due refresh and reports to the Observer exactly likeRequireAPI; a refused refresh ends the session in the store and surfaces as the error. See Sessions. (#13)
No breaking changes.
v1.2.0
Moves the library to github.com/govengers/wide-event-logger v1. Upgrade the logger together with auth; see docs/migration.md (D62). The GitHub wiki now mirrors the docs of every release.
Breaking changes
- ginadapter / wide events:
ObserveandRequirerecord on events ofgithub.com/govengers/wide-event-loggerv1 instead ofgithub.com/mxcd/go-wide-event-loggerv0.3.x. Mount the logger's v1 gin middleware; with the old middleware the code still compiles, but nothing is recorded. - ginadapter:
RedactQuerytakes the v1*wideevent.Eventand is a deprecated no-op: the v1 middleware redacts every query value itself unless the parameter is inOptions.QueryAllowlist. - forwardauth image events:
durationreplacesresponse.latencyandresponse.latency_ms;nameishttp.request;response.gin_errorsis a list;request.idis generated whenX-Request-IDis missing or invalid and echoed in the response header; method, host, path and client IP are capped at 32, 256, 2048 and 64 bytes. Adjust dashboards and alerts that read the old fields.
Changed
- forwardauth records only
service.versionandforward.*itself; the logger records and caps the request fields, and each field is emitted once. - The wiki is published by CI for every newest stable release from that release's
docs/wiki(D63).
Image: ghcr.io/govengers/forwardauth:1.2.0.
v1.1.1
Security fix for the password provider. No API or configuration change.
Fixed
- password: a login pending its second factor (or its TOTP enrollment) is now bound to the credentials its password step verified. When the password or the second factor changes before the second step, TOTP verify ends the pending login with
no_pending_login, and setup and enable of a pending enrollment refuse the same way. Before, an attacker holding the old password plus a TOTP or backup code could complete a login started before a password reset withinPendingTTL(default 5 minutes) and keep a session that passed every refresh (D61). Found by security review run 3.
Upgrade notes
- Logins that are waiting for their second factor while you deploy carry no credential stamp and answer
no_pending_loginonce; the user logs in again.
Image: ghcr.io/govengers/forwardauth:1.1.1 (the image has no password provider; it is rebuilt for the release).
v1.1.0
Security hardening from audit run 2 and impersonation as a library construct. Some changes break code or configuration that relied on the old behaviour; each is listed below and in docs/migration.md under "After v1.0.0". Decisions D42-D60 in docs/design.md.
New: impersonation (session.Impersonator, D60)
session.NewImpersonator(&session.ImpersonatorOptions{Guard, Load, ...})is the only way to start or stop an impersonation:POST /auth/impersonation {"subject"}andDELETE /auth/impersonationbehind the Guard, orStart/Stopin your own handlers.Actor(defaultauthorization.IsAdmin()) decides who may impersonate,Target(defaultNot(IsAdmin())) who may be impersonated. Always enforced: no nesting, active impersonator and target, no self-impersonation, a swap against the identity the Guard read.- An impersonation ends when stopped, with the session, or after
MaximumDuration(default 1 h); the next request runs as the impersonator again. - JWTs of an impersonating identity carry the impersonator as the RFC 8693
actclaim;Verifyreturns it asIdentity.Impersonator(without roles). - New client codes
already_impersonating,not_impersonating,unknown_impersonation_target,self_impersonation; outcomesimpersonation_start,impersonation_stop; wide-event keysauth.impersonation.*andauth.impersonated_subject.
Breaking changes
- session:
SaveIdentityandSwapIdentityreturn an error for an identity or impersonator of another provider or subject than the session holds, and a login cannot save an impersonating identity. Replace code that setIdentity.Impersonatoritself withsession.Impersonator; impersonations stored the old way end at their next request (D60). - jwt:
Signrefuses an identity whose impersonator is inactive;authorization.Real(...)on a verified token now sees theactimpersonator (D60).VerifierOptions.Audienceis required (D54).JWKSURLmust behttps(plainhttpon loopback only) unlessInsecureJWKSURL: true(D54). NewMaximumKeyAge(default 24 h): keys are no longer trusted forever while the JWKS is unreachable (D55). - oidc:
Issuer: oidc.GoogleIssuerneedsAllowedEmailDomains,OnLoginorAllowAnyAccount: true; with Google, a non-consumer allowed domain also requires thehdclaim, so only Workspace accounts of that domain pass (D56). - password: sessions are bound to a credential stamp; a password change or TOTP enable/disable ends the user's other sessions, and every password user logs in once more after the upgrade (D45).
- session:
ParseKeyandNewStorerefuse an encryption key that repeats one byte (all zero included) (D44);SaveIdentityandSwapIdentityno longer reset the Guard's refresh clock (D44). - authorization: every check except
IsAuthenticatedrefuses an identity whose impersonator is inactive (D47). - forwardauth image:
JWT_AUDIENCErequired withJWT_ENABLED=true(D54); Google needsOIDC_ALLOWED_EMAIL_DOMAINS, an enforcedKEYCLOAK_REQUIRED_*list orOIDC_ALLOW_ANY_ACCOUNT=true(D56); percent-encoded route variants answer 404 and/auth/merefuses inactive identities (D50); the checks refuse an identity whose impersonator is inactive (D60);request.client_ipis the peer address unlessTRUSTED_PROXIESlists it (D51); a malformedSESSION_REDIS_ADDRESSfails validation at boot.
Security fixes (audit run 2)
- Memory backend evicts through an expiry heap (O(log n)), so login floods no longer stall every request (D42).
- Redis backend: an evicted session index reads as logged out, so a logout always takes effect (D43).
- Guard: application saves no longer postpone the refresh; a failed refresh claim calls the provider only when it committed (D44).
- OIDC access-token roles only for the same
sub; raw Keycloak role claims are not copied into JWTs (D48); logout work bounded at 10 s; the Prometheus adapter never panics on invalid label values (D49). - GitHub OAuth tokens are revoked whenever the Guard or the provider ends a session (
session.Revoker, D57).
Forward-auth image
- Redis Sentinel (
SESSION_REDIS_SENTINEL_*), TLS including mTLS (SESSION_REDIS_TLS_*) and an ACL username (SESSION_REDIS_USERNAME) (D52, D53). TRUSTED_PROXIES(default none) decides whoseX-Forwarded-Forthe logged client IP trusts (D51).- Published only after lint, tests, e2e and vulncheck passed, with a signed build provenance attestation;
:latest,:1and:1.1move only for the newest stable tag (D59).
Image: ghcr.io/govengers/forwardauth:1.1.0.
v1.0.0
First release of github.com/govengers/auth. It merges mxcd/go-basicauth (v1.5.x), mxcd/oidc-fwd-auth (v0.9.x) and mxcd/github-fwd-auth (v1.4.x) into one module and adds authorization checks, resource permissions, API keys, a gin and a Prometheus adapter, a Redis session backend and the forward-auth image. There are no compatibility shims; docs/migration.md maps every old symbol and environment variable and gives a recipe per library.
Breaking changes against all three predecessors:
- New import path
github.com/govengers/auth/...; Go 1.26. - Sessions are server-side for every provider; the cookie holds a random ID only; the lifetime is
StoreOptions.MaximumAge(Store.MaximumAge(),SESSION_MAXIMUM_AGE_SECONDS). Existing sessions are invalid: every user logs in once after the upgrade. The cookie-only mode,gorilla/sessionsand the signing keys are gone; one 32-byteEncryptionKeyremains (openssl rand -base64 32). An ownsession.Backendimplements five methods,CompareAndSwapincluded;ErrSessionRevokedissession.ErrRevoked. - Session cookie: a Secure, host-only cookie on
/is named__Host-<CookieName>(__Host-auth_session,__Host-forwardauth_session);SameSiteDefault is refused, andStrictis refused byoidc.Newandgithub.New(the callback would arrive without the cookie), so it is for password-only stores. A refresh that keeps failing for longer thanGuardOptions.MaximumUnverifiedAge(default 1 h,SESSION_MAXIMUM_UNVERIFIED_AGE_SECONDS) ends the session. - One identity type,
auth.Identity, replacesbasicauth.User,oidc.SessionData,jwt.Userand the GitHub session user. Onesession.Guardreplaces the per-provider middleware; there is no global middleware and noPathRules, you mount the Guard on route groups. - Route prefixes:
/auth/password/*,/auth/oidc/<name>/*,/auth/github/*, each aPathPrefixof plain segments without a trailing/. Redirect URIs are derived fromExternalURL; register the new callback URLs at your IdP and GitHub.ErrorURLandPostLogoutURLbecome absolute and refuse fragments. - One error body
{"code","message"}with stable codes (tfa_*becametotp_*,unauthorizedbecameunauthenticated); translatedMessagesare gone. - Hooks are
auth.Hook(OnLogin) andauth.LogoutHook;UserTransformeris an application middleware after the Guard. Libraries no longer log; they call an optionalauth.Observer. password: default minimum length 12 characters without composition rules;Storagetakes acontext.Context, gainsUpdatePasswordHash(compare-and-swap),ConsumeTOTPStep(atotp_stepcolumn; every TOTP code is accepted once) andErrUserExists, matches usernames and emails ignoring case, andSetTOTPreturns(bool, error): it enables TOTP only while it is off, atomically (... WHERE id = $1 AND NOT totp_enabled);OnLoginmust keepUserID;TOTP.Requiredenrolls during login and registration; TOTP routes exist only withTOTP.Enabled, and enrolled users cannot log in while it is off; backup codes are checked only with"backupCode": true(no fallthrough from a wrong TOTP code);MaximumAttemptsis a lockout across logins until a verified code orResetTOTPAttempts, with no unlimited mode; new limitsPeriod<= 3600,Skew<= 10,BackupCodeCount<= 20, backup codes <= 128 bytes and >= 1,000,000 distinct codes; Argon2id hashes on outdated parameters are rehashed at login.- OIDC and GitHub: PKCE (and nonce) always on, GitHub logout is
GET, logout (also password logout) refusesSec-Fetch-Site: cross-sitewith 403, so logout links must live on the app's own site, GitHubAllowedTeamsis required (an empty list no longer admits everyone), GitHubScopesmust include one that lists teams (read:org,write:org,admin:org,user,repo), the endpoints OIDC discovery names must behttps(or loopbackhttp), no component follows a redirect of its token, discovery, JWKS, Keycloak, GitHub revocation or GitHub user and team endpoint (point the URLs at the final location), Keycloak enrichment moved from gocloak tooidc/keycloakand defaults to client credentials, GitHubUserProfileand friends are gone (Identity.Teams,HTTPClient). - API keys are named, at least 16 bytes, unique, and grant only the roles and permissions you give them (the old "API key bypasses admin" behaviour needs
Roles: []string{auth.RoleAdmin}).Authorization: Beareris matched in any case, so a foreign bearer token on a route behindapikeyis 401. - JWT: RS512 only, jwx v3,
exprequired, default lifetime 5 minutes (was 30), issuer defaults toEXTERNAL_URL, GitHubsubis the numeric user ID and the login ispreferred_username(uidis gone), JWKS at/auth/jwks, no generated signing key,Signrefuses an inactive identity, the verifier uses only RSA JWKS keys with an RS*/PS*algand akidand answers 401 withWWW-Authenticate: Bearer. - Forward-auth: one image
ghcr.io/govengers/forwardauthselected byPROVIDERreplacesoidc-fwd-authandgithub-fwd-auth; renamed environment variables, new routes (/auth/ui,/auth/api,/auth/jwks), an explicit Traefik router for/auth(without the checks and probes) is required, Google multi-provider mode is removed,JWT_PRIVATE_KEYis required when JWT is on, API keys needAPI_KEYS_ENABLED=true(API_KEYSalone is ignored) and carry permissions (API_KEY_PERMISSIONS) but no roles.EXTERNAL_URLis an origin without a path, a route with a trailing/is 404 (no redirect),SESSION_SAME_SITEtakeslaxornone,SHUTDOWN_DELAY_SECONDSat most 10, the boot fails when Redis does not answer or when a plainhttpEXTERNAL_URLoff loopback comes withoutSESSION_INSECURE_COOKIE=true; listX-API-Keynext toAuthorizationinauthResponseHeaders.
New: authorization (incl. HasPermission), resource permissions (auth.Permission, ginadapter.RequireMethodPermission, the JWT permissions claim), apikey, ginadapter, prometheusadapter, session/redisbackend, TOTP enrollment during login, Refreshers that re-check teams and users every minute, IdentityHandler, wide-event and Prometheus reporting in the image, ginadapter.RedactQuery (redacts query values on the wide event; mount it or OAuth callback codes reach your logs). Agent docs: llms.txt, docs/wiki/, runnable examples/.
Migration guide: docs/migration.md. Image: ghcr.io/govengers/forwardauth:1.0.0.