Add the #162 fix to the changelog. Document that a rollback to v1.0.25 or earlier invalidates sessions, because those releases cannot read the encrypted cookies from v1.0.26. Tell operators which cookies to delete. Claude-Session: https://claude.ai/code/session_01G1d1n1BrHHHw8dBUkbiBPC
8.4 KiB
Changelog
All notable changes to this project are documented in this file.
The format follows Keep a Changelog.
[Unreleased]
Added
-
allowUnauthenticatedPreflight(defaultfalse). EveryOPTIONSrequest needs authentication by default, as in the last release. Set the option totrueto let a genuine CORS preflight reach the backend without a session. Only anOPTIONSrequest that carries bothOriginandAccess-Control-Request-Methodbypasses authentication, and the plugin discards the backend's response body for it, because a browser never reads a preflight body (settings.go,middleware.go, FIX-02). See the option table in CONFIGURATION.md. -
streamingRefresh(defaultfalse). SSE and WebSocket requests do not check the token expiry, so a session stays usable for streaming until the session cookie expires. Set the option totrueto refresh an expired token on these requests. If the IdP rejects the refresh withinvalid_grant, the request gets401. If the IdP cannot be reached, the request is forwarded as before. The request waits for the full refresh, so a rotated refresh token is never lost (middleware.go).
Changed
-
Opaque bearer tokens when all three bearer flags are set. A deployment that sets
enableBearerAuth,requireTokenIntrospectionandallowOpaqueTokensnow accepts opaque bearer tokens after RFC 7662 introspection. The last release rejected every non-JWT bearer token. The plugin binds an accepted opaque token to the client throughclient_idoraud. It also applies the identifier, token-age, logout, revocation andnbfchecks (bearer_auth.go). -
Sessions created before this upgrade can omit
post_logout_redirect_urion their first logout. The plugin now builds that parameter only from the redirect-URI origin recorded at login, never from the logout request's host. Older sessions have no recorded origin, so those users stay on the IdP's logout page once (helpers.go). -
Shutdown waits up to 5 seconds for a token refresh in progress. On shutdown, for example a Traefik reload, the refresh coordinator lets a refresh that already reached the IdP deliver its tokens, then returns (
shutdownRefreshDrainTimeout,refresh_coordinator.go). A refresh that has not started does not start after shutdown. -
A panic after the request reaches the backend now aborts the connection. The plugin re-panics it, for example the reverse proxy's
http.ErrAbortHandlerwhen an upstream drops the connection mid-body. Go's HTTP server then aborts the connection. Before, the plugin recovered the panic, so a truncated response could reach the client as a complete 200. A panic before the request reaches the backend still returns 500 (middleware.go). -
private_key_jwt now rejects RSA signing keys under 2048 bits.
validateAlgKeyMatchstopsNew()with an error when a configuredprivate_key_jwtRS*/PS* key is smaller than 2048 bits (RFC 7518 §3.3). A smaller key signs locally, but a conformant IdP rejects it at token exchange withinvalid_client, so the plugin now fails at construction instead of at every runtime exchange. There is no override: deployments using an RSA key under 2048 bits must generate a 2048-bit-or-larger key before upgrading. The rejection returns a matchableErrRSAKeyTooSmallsentinel (client_assertion.go). -
A discovered
http://endpoint is now dropped whenproviderURLishttps://.validateDiscoveredEndpointrefuses to accept an authorization, token, jwks_uri, revocation, end_session, introspection, or registration endpoint served over plaintext HTTP when the operator configured an HTTPS provider (R146): using it as-is would send a client secret or token over an unauthenticated channel. There is no configuration flag to disable this check. A dropped endpoint logs anERRORline; a droppedtoken,jwks_uri, orauthorizationendpoint — the three every login needs — additionally logs aSECURITY:-prefixed line naming the endpoint, the dropped URL, and the fact that there is no override, so the failure is diagnosable instead of surfacing only as a broken login. See Discovered Endpoint Validation (url_helpers.go,main.go). -
Blacklist entries in Redis use the
blacklist:key prefix. The blacklist cache now has its own cache type (CacheTypeBlacklist), so its Redis keys move from the sharedtoken:prefix toblacklist:(R128). A cached token entry and a revocation marker for the same raw token can no longer overwrite each other. For one release, a blacklist miss also reads the legacytoken:<key>entry and treats it as revoked only when the stored value is booleantrue(checkLegacyBlacklistMarkerinuniversal_cache.go, FIX-16). The fallback works in one direction only: during a rolling deploy, a replica that runs the previous version does not see revocations that an upgraded replica writes underblacklist:. A blacklist miss checks the legacytoken:entry at most once per key in each 30 seconds, so the fallback adds little Redis traffic. -
Claim validation accepts four previously-rejected or silently-dropped claim shapes. Each change below is intentional, matches a production change, and is pinned by an existing regression test.
iat(issued-at) is optional injwt.Verify, per RFC 7519 §4.1.6. A token that omits it is validated oniss/aud/exp/nbfalone, instead of being rejected (jwt.go, R126). The lenient-audience access-token path (accessTokenUnexpiredintoken_validation_rs.go) applies the same rule, because both paths callverifyTimeClaims(jwt.go, FIX-27). Bearer-token auth still rejects a token with noiat: it returns"missing iat claim"(enforceIatAgeinbearer_auth.go), becausemaxTokenAgeSecondsalways applies —0falls back to a 24h default (main.go).- A numeric
sub(bearer-auth identifier) claim is stringified instead of rejected, so an IdP that emits a numeric subject still authenticates (bearer_auth.goresolveBearerIdentifier, R102). - A
groups/rolesclaim that is neither a string nor an array (for example a bare number ornull) is treated as an empty list instead of failing claim extraction outright, so a malformed claim on one side does not suppress a valid claim on the other (token_manager.goextractGroupsAndRolesFromClaims, R96). - A numeric element inside a
groups/rolesarray is stringified into the list instead of being silently dropped, so a numeric group ID survives extraction and can match anallowedRolesAndGroupsentry (token_manager.gostringListFromClaim,utilities.goclaimScalarString, R105).
Fixed
- SSE and WebSocket requests now get the configured
headers, as normal requests do. The last release sent onlyX-Forwarded-Useron these requests, so a backend that checks a shared-secret header answered401on WebSocket upgrades. The same requests now also honor back-channel and front-channel logout (middleware.go, #162). - The opaque bearer path rejects a token that
RevokeTokenadded to the blacklist, for example at logout. It also rejects an opaque token whosenbfis in the future (bearer_auth.go). - The backchannel-logout replay check treats a Redis error reply such as
READONLY,OOMorMISCONFas a failure, not as an unknown outcome. A replayed logout token is now rejected while Redis refuses writes (internal/cache/backends/redis.go). - After a failed cache write, a replica no longer ignores a newer value that
another replica wrote, such as a later logout. The preference for the local
value expires after 5 seconds (
backendStaleMarkTTL,universal_cache.go). - The token circuit breaker treats HTTP 408 from the token endpoint as a
transient failure, the same as 429 (
error_recovery.go). - With
allowUnauthenticatedPreflightenabled, a preflight response no longer carries a staleContent-Lengthwhen the backend writes a body without callingWriteHeader(middleware.go). - A dropped
http://revocation, end-session or introspection endpoint logs theSECURITY:line when its override (revocationURL,oidcEndSessionURLorintrospectionURL) is not configured (main.go).