Skip to content

Commit 7994e06

Browse files
authored
Merge pull request #26 from magfest/fix/oauth-stale-session-handling
Fix false login failures caused by stale OAuth session state
2 parents 4908e2e + b0b62ba commit 7994e06

File tree

5 files changed

+710
-664
lines changed

5 files changed

+710
-664
lines changed

app/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ def create_app() -> Flask:
155155
app.config["SLACK_BOT_TOKEN"] = os.environ.get("SLACK_BOT_TOKEN")
156156
app.config["SLACK_CHANNEL_ID"] = os.environ.get("SLACK_CHANNEL_ID")
157157

158-
# --- Proxy Fix for AWS AppRunner ---
158+
# --- Proxy Fix for reverse proxies (Heroku, AWS, etc.) ---
159159
if os.environ.get("BEHIND_PROXY", "false").lower() == "true":
160160
from werkzeug.middleware.proxy_fix import ProxyFix
161161
app.wsgi_app = ProxyFix(

app/routes/auth.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
from flask import Blueprint, redirect, url_for, session, flash, current_app, request, render_template
1414
from authlib.integrations.flask_client import OAuth
15+
from authlib.integrations.base_client import MismatchingStateError
1516

1617
from app import db
1718

@@ -171,6 +172,11 @@ def _handle_google_callback():
171172

172173
try:
173174
token = oauth.google.authorize_access_token()
175+
except MismatchingStateError:
176+
current_app.logger.info('Google state mismatch — stale session, retrying OAuth')
177+
log_login_failure("stale_session", provider="google", severity="INFO")
178+
db.session.commit()
179+
return redirect(url_for('auth.login'))
174180
except Exception as e:
175181
current_app.logger.error(f'Google OAuth error: {e}')
176182
log_login_failure("oauth_error", provider="google")
@@ -217,6 +223,14 @@ def _handle_keycloak_callback():
217223

218224
try:
219225
token = oauth.keycloak.authorize_access_token()
226+
except MismatchingStateError:
227+
# Stale session: user's Flask session expired but Keycloak SSO session
228+
# was still active, so the callback state doesn't match. This is benign
229+
# — just re-initiate the OAuth flow and it will succeed on the retry.
230+
current_app.logger.info('Keycloak state mismatch — stale session, retrying OAuth')
231+
log_login_failure("stale_session", provider="keycloak", severity="INFO")
232+
db.session.commit()
233+
return redirect(url_for('auth.login'))
220234
except Exception as e:
221235
current_app.logger.error(f'Keycloak OAuth error: {e}')
222236
log_login_failure("oauth_error", provider="keycloak")

app/security_audit.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,18 +105,20 @@ def log_login_success(user_id: str, provider: str, email: str) -> SecurityAuditL
105105
)
106106

107107

108-
def log_login_failure(reason: str, email: str = None, provider: str = None) -> SecurityAuditLog:
108+
def log_login_failure(reason: str, email: str = None, provider: str = None,
109+
severity: str = SEVERITY_WARNING) -> SecurityAuditLog:
109110
"""Log failed authentication attempt.
110111
111112
Args:
112-
reason: Why the login failed (e.g., 'domain_restricted', 'oauth_error')
113+
reason: Why the login failed (e.g., 'domain_restricted', 'oauth_error', 'stale_session')
113114
email: Email that attempted to log in (if known)
114115
provider: OAuth provider that was used (if known)
116+
severity: Severity level (default WARNING, use INFO for benign failures like stale_session)
115117
"""
116118
return log_security_event(
117119
EVENT_LOGIN_FAILURE,
118120
CATEGORY_AUTH,
119-
SEVERITY_WARNING,
121+
severity,
120122
user_id=None,
121123
details={"reason": reason, "email": email, "provider": provider},
122124
)

0 commit comments

Comments
 (0)