@@ -408,6 +408,14 @@ def google_login():
408408
409409 # We generate our own state so we can key Redis storage on it reliably
410410 state = secrets .token_urlsafe (32 )
411+ session ["google_oauth_state" ] = state
412+
413+ # NEW: also persist the state server-side so we can validate without session
414+ try :
415+ r = _redis_client ()
416+ r .setex (f"oidc:state:{ state } " , 600 , "1" )
417+ except Exception as e :
418+ current_app .logger .warning ("Failed to cache OAuth state in Redis: %s" , e )
411419
412420 oauth = OAuth2Session (
413421 client_id ,
@@ -455,6 +463,20 @@ def google_callback():
455463 # --- CSRF (state) check ---
456464 state = request .args .get ("state" )
457465 expected_state = session .pop ("google_oauth_state" , None )
466+
467+ # NEW: accept state if present in Redis (one-time)
468+ state_ok = False
469+ try :
470+ r = _redis_client ()
471+ if r .get (f"oidc:state:{ state } " ):
472+ state_ok = True
473+ r .delete (f"oidc:state:{ state } " ) # one-time use
474+ except Exception as e :
475+ current_app .logger .warning ("Could not read OAuth state from Redis: %s" , e )
476+
477+ if not state or not (state == expected_state or state_ok ):
478+ flash ("State mismatch. Authentication failed." , "danger" )
479+ return redirect (url_for ("auth.login" ))
458480 if not state or state != expected_state :
459481 flash ("State mismatch. Authentication failed." , "danger" )
460482 return redirect (url_for ("auth.login" ))
0 commit comments