@@ -5,6 +5,7 @@ import (
55 "errors"
66 "fmt"
77 "net/http"
8+ "strings"
89 "time"
910
1011 "github.com/justinas/alice"
@@ -31,6 +32,33 @@ const (
3132 sessionRefreshRetryPeriod = 10 * time.Millisecond
3233)
3334
35+// isFatalRefreshError checks if a refresh error indicates a revoked or
36+// non-existent session that should be immediately invalidated.
37+// Fatal errors indicate the session is no longer valid at the provider level.
38+// Non-fatal errors (network issues, timeouts) should not invalidate the session.
39+//
40+// Only checks standard OAuth2 error codes (RFC 6749 Section 5.2).
41+// Does NOT check error_description strings as they are optional and provider-specific.
42+func isFatalRefreshError(err error) bool {
43+ if err == nil {
44+ return false
45+ }
46+
47+ // Only check standard OAuth2 error codes (RFC 6749 Section 5.2)
48+ // Do NOT check error_description strings as they are optional and provider-specific
49+ fatalErrors := []string{
50+ "invalid_grant", // refresh token revoked, expired, or session terminated
51+ "invalid_client", // client credentials no longer valid
52+ }
53+
54+ for _, fe := range fatalErrors {
55+ if strings.Contains(err.Error(), fe) {
56+ return true
57+ }
58+ }
59+ return false
60+}
61+
3462// StoredSessionLoaderOptions contains all of the requirements to construct
3563// a stored session loader.
3664// All options must be provided.
@@ -188,9 +216,24 @@ func (s *storedSessionLoader) refreshSessionIfNeeded(rw http.ResponseWriter, req
188216 // We are holding the lock and the session needs a refresh
189217 logger.Printf("Refreshing session - User: %s; SessionAge: %s", session.User, session.Age())
190218 if err := s.refreshSession(rw, req, session); err != nil {
191− // If a preemptive refresh fails, we still keep the session
192− // if validateSession succeeds.
193219 logger.Errorf("Unable to refresh session: %v", err)
220+
221+ // Check if this is a fatal error that indicates the session is revoked
222+ // or no longer valid at the provider level
223+ if isFatalRefreshError(err) {
224+ logger.Printf("Fatal refresh error detected (session revoked or invalid), clearing session for user: %s", session.User)
225+
226+ // Clear the session from storage (Redis) and remove the cookie
227+ if err := s.store.Clear(rw, req); err != nil {
228+ logger.Errorf("failed clearing session: %v", err)
229+ }
230+
231+ // Return error immediately to force re-authentication
232+ return fmt.Errorf("session invalidated due to fatal refresh error: %w", err)
233+ }
234+
235+ // For non-fatal errors (network issues, timeouts), keep the session
236+ // and let validateSession determine if it's still usable
194237 }
195238
196239 // Validate all sessions after any Redeem/Refresh operation (fail or success)