Created
February 6, 2026 17:10
-
-
Save frhack/f5664c652e9e0f65a48cae969c97ff56 to your computer and use it in GitHub Desktop.
Fix #1945: invalidate session on fatal refresh error (invalid_grant/Session not active) - OAuth2-Proxy v7.14.2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Stage 1: Build the patched oauth2-proxy binary | |
| FROM golang:1.22-alpine AS builder | |
| # Install build dependencies | |
| RUN apk add --no-cache git make | |
| # Set working directory | |
| WORKDIR /build | |
| # Allow Go to download the required toolchain version | |
| ENV GOTOOLCHAIN=auto | |
| # Copy go mod files first for better caching | |
| COPY go.mod go.sum ./ | |
| RUN go mod download | |
| # Copy the entire source code (including the patched stored_session.go) | |
| COPY . . | |
| # Build the binary | |
| RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -ldflags="-w -s" -o oauth2-proxy . | |
| # Stage 2: Create the final image | |
| FROM alpine:3.19 | |
| # Install runtime dependencies | |
| RUN apk add --no-cache ca-certificates curl | |
| # Copy the binary from builder | |
| COPY --from=builder /build/oauth2-proxy /bin/oauth2-proxy | |
| # Use non-root user for security | |
| RUN addgroup -g 2000 -S oauth2-proxy && \ | |
| adduser -u 2000 -S oauth2-proxy -G oauth2-proxy | |
| USER oauth2-proxy | |
| ENTRYPOINT ["/bin/oauth2-proxy"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| OAuth2-Proxy Session Invalidation on Fatal Refresh Errors | |
| ========================================================== | |
| Description: | |
| Fix for GitHub Issue #1945: Invalidate session when refresh token fails | |
| with fatal errors like "invalid_grant" or "Session not active" from OIDC providers. | |
| Problem: | |
| When a session is invalidated at the provider level (e.g., admin revokes session | |
| in Keycloak), OAuth2-Proxy continues serving the local cached session indefinitely, | |
| keeping users logged in even though their provider session no longer exists. | |
| Solution: | |
| This patch distinguishes between fatal errors (revoked/invalid sessions) and | |
| transient errors (network issues). When a fatal error is detected during token | |
| refresh, the session is immediately cleared from storage (Redis/Cookie) and the | |
| user is redirected to login. | |
| Fatal Errors (session cleared): | |
| - "invalid_grant" | |
| - "Session not active" | |
| - "Token is not active" | |
| - "invalid_client" | |
| Transient Errors (session kept): | |
| - Network timeouts | |
| - Provider temporarily unreachable (5xx errors) | |
| - DNS errors | |
| Target Version: v7.14.2 | |
| Tested With: Keycloak OIDC provider, Redis session store | |
| Issue: https://github.com/oauth2-proxy/oauth2-proxy/issues/1945 | |
| Date: 2026-02-06 | |
| How to Apply: | |
| cd oauth2-proxy | |
| git checkout v7.14.2 | |
| git apply oauth2-proxy-fix-1945.patch | |
| # Or using patch command: | |
| patch -p1 < oauth2-proxy-fix-1945.patch | |
| Files Modified: | |
| pkg/middleware/stored_session.go | |
| Changes: | |
| - Added import: "strings" | |
| - Added helper function: isFatalRefreshError() | |
| - Modified refreshSessionIfNeeded() to clear session on fatal errors | |
| --- | |
| diff --git a/pkg/middleware/stored_session.go b/pkg/middleware/stored_session.go | |
| index f861c756..f5e9125d 100644 | |
| --- a/pkg/middleware/stored_session.go | |
| +++ b/pkg/middleware/stored_session.go | |
| @@ -5,6 +5,7 @@ import ( | |
| "errors" | |
| "fmt" | |
| "net/http" | |
| + "strings" | |
| "time" | |
| "github.com/justinas/alice" | |
| @@ -31,6 +32,31 @@ const ( | |
| sessionRefreshRetryPeriod = 10 * time.Millisecond | |
| ) | |
| +// isFatalRefreshError checks if a refresh error indicates a revoked or | |
| +// non-existent session that should be immediately invalidated. | |
| +// Fatal errors indicate the session is no longer valid at the provider level. | |
| +// Non-fatal errors (network issues, timeouts) should not invalidate the session. | |
| +func isFatalRefreshError(err error) bool { | |
| + if err == nil { | |
| + return false | |
| + } | |
| + | |
| + errStr := err.Error() | |
| + fatalErrors := []string{ | |
| + "invalid_grant", | |
| + "Session not active", | |
| + "Token is not active", | |
| + "invalid_client", | |
| + } | |
| + | |
| + for _, fe := range fatalErrors { | |
| + if strings.Contains(errStr, fe) { | |
| + return true | |
| + } | |
| + } | |
| + return false | |
| +} | |
| + | |
| // StoredSessionLoaderOptions contains all of the requirements to construct | |
| // a stored session loader. | |
| // All options must be provided. | |
| @@ -188,9 +214,25 @@ func (s *storedSessionLoader) refreshSessionIfNeeded(rw http.ResponseWriter, req | |
| // We are holding the lock and the session needs a refresh | |
| logger.Printf("Refreshing session - User: %s; SessionAge: %s", session.User, session.Age()) | |
| if err := s.refreshSession(rw, req, session); err != nil { | |
| - // If a preemptive refresh fails, we still keep the session | |
| - // if validateSession succeeds. | |
| logger.Errorf("Unable to refresh session: %v", err) | |
| + | |
| + // Check if this is a fatal error that indicates the session is revoked | |
| + // or no longer valid at the provider level | |
| + if isFatalRefreshError(err) { | |
| + logger.Printf("Fatal refresh error detected (session revoked or invalid), clearing session for user: %s", session.User) | |
| + | |
| + // Clear the session from storage (Redis) and remove the cookie | |
| + clearErr := s.store.Clear(rw, req) | |
| + if clearErr != nil { | |
| + logger.Errorf("Error clearing session: %v", clearErr) | |
| + } | |
| + | |
| + // Return error immediately to force re-authentication | |
| + return fmt.Errorf("session invalidated due to fatal refresh error: %w", err) | |
| + } | |
| + | |
| + // For non-fatal errors (network issues, timeouts), keep the session | |
| + // and let validateSession determine if it's still usable | |
| } | |
| // Validate all sessions after any Redeem/Refresh operation (fail or success) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment