@@ -210,11 +210,11 @@ impl OidcState {
210210 async fn get_token_claims (
211211 & self ,
212212 id_token : OidcToken ,
213- state : Option < & OidcLoginState > ,
213+ expected_nonce : Option < & Nonce > ,
214214 ) -> anyhow:: Result < OidcClaims > {
215215 let client = & self . get_client ( ) . await ;
216216 let verifier = self . config . create_id_token_verifier ( client) ;
217- let nonce_verifier = |nonce : Option < & Nonce > | check_nonce ( nonce, state ) ;
217+ let nonce_verifier = |nonce : Option < & Nonce > | check_nonce ( nonce, expected_nonce ) ;
218218 let claims: OidcClaims = id_token
219219 . into_claims ( & verifier, nonce_verifier)
220220 . map_err ( |e| anyhow:: anyhow!( "Could not verify the ID token: {}" , e) ) ?;
@@ -379,11 +379,8 @@ async fn handle_oidc_callback(oidc_state: &OidcState, request: ServiceRequest) -
379379 // Try to get redirect URL from query params if available
380380 let redirect_url =
381381 if let Ok ( params) = Query :: < OidcCallbackParams > :: from_query ( query_string) {
382- if let Ok ( state) = get_state_from_cookies ( & request, & params. state ) {
383- state. initial_url
384- } else {
385- "/" . to_string ( )
386- }
382+ get_redirect_url_from_cookie ( & request, & params. state )
383+ . unwrap_or_else ( |_| "/" . to_string ( ) )
387384 } else {
388385 "/" . to_string ( )
389386 } ;
@@ -398,16 +395,13 @@ async fn handle_oidc_callback(oidc_state: &OidcState, request: ServiceRequest) -
398395/// When an user has already authenticated (potentially in another tab), we ignore the callback and redirect to the initial URL.
399396fn handle_authenticated_oidc_callback ( request : ServiceRequest ) -> ServiceResponse {
400397 // Try to get redirect URL from query params if available
401- let redirect_url =
402- if let Ok ( params) = Query :: < OidcCallbackParams > :: from_query ( request. query_string ( ) ) {
403- if let Ok ( state) = get_state_from_cookies ( & request, & params. state ) {
404- state. initial_url
405- } else {
406- "/" . to_string ( )
407- }
408- } else {
409- "/" . to_string ( )
410- } ;
398+ let redirect_url = if let Ok ( params) =
399+ Query :: < OidcCallbackParams > :: from_query ( request. query_string ( ) )
400+ {
401+ get_redirect_url_from_cookie ( & request, & params. state ) . unwrap_or_else ( |_| "/" . to_string ( ) )
402+ } else {
403+ "/" . to_string ( )
404+ } ;
411405 log:: debug!( "OIDC callback received for authenticated user. Redirecting to {redirect_url}" ) ;
412406 request. into_response ( build_redirect_response ( redirect_url) )
413407}
@@ -450,34 +444,23 @@ async fn process_oidc_callback(
450444 } ) ?
451445 . into_inner ( ) ;
452446
453- let state = get_state_from_cookies ( request, & params. state )
454- . context ( "Failed to read oidc state cookies " ) ?;
447+ let redirect_url = get_redirect_url_from_cookie ( request, & params. state )
448+ . context ( "Failed to read redirect URL from cookie " ) ?;
455449
456450 let client = oidc_state. get_client ( ) . await ;
457451 log:: debug!( "Processing OIDC callback with params: {params:?}. Requesting token..." ) ;
458- let token_response = exchange_code_for_token ( & client, http_client, params) . await ?;
452+ let token_response = exchange_code_for_token ( & client, http_client, params. clone ( ) ) . await ?;
459453 log:: debug!( "Received token response: {token_response:?}" ) ;
460454
461- let redirect_target = validate_redirect_url ( state . initial_url ) ;
455+ let redirect_target = validate_redirect_url ( redirect_url ) ;
462456 log:: info!( "Redirecting to {redirect_target} after a successful login" ) ;
463457 let mut response = build_redirect_response ( redirect_target) ;
464458 set_auth_cookie ( & mut response, & token_response, oidc_state)
465459 . await
466460 . context ( "Failed to set auth cookie" ) ?;
467461
468462 // Clean up the state-specific cookie after successful authentication
469- let state_cookie_name = format ! (
470- "{}{}" ,
471- SQLPAGE_STATE_COOKIE_PREFIX ,
472- state. csrf_token. secret( )
473- ) ;
474- let cleanup_cookie = Cookie :: build ( state_cookie_name, "" )
475- . secure ( true )
476- . http_only ( true )
477- . same_site ( actix_web:: cookie:: SameSite :: Lax )
478- . path ( "/" )
479- . max_age ( actix_web:: cookie:: time:: Duration :: seconds ( 0 ) ) // Expire immediately
480- . finish ( ) ;
463+ let cleanup_cookie = create_cleanup_cookie ( & params. state ) ;
481464 response. add_cookie ( & cleanup_cookie) . unwrap ( ) ;
482465
483466 Ok ( response)
@@ -539,8 +522,8 @@ async fn build_auth_provider_redirect_response(
539522 initial_url : String ,
540523) -> HttpResponse {
541524 let AuthUrl { url, params } = build_auth_url ( oidc_state) . await ;
542- let state = OidcLoginState :: new ( initial_url , params) ;
543- let ( nonce_cookie , redirect_cookie) = create_state_cookies ( & state ) ;
525+ let nonce_cookie = create_nonce_cookie ( & params. nonce ) ;
526+ let redirect_cookie = create_redirect_cookie ( & params . csrf_token , & initial_url ) ;
544527 HttpResponse :: TemporaryRedirect ( )
545528 . append_header ( ( "Location" , url. to_string ( ) ) )
546529 . cookie ( nonce_cookie)
@@ -566,10 +549,10 @@ async fn get_authenticated_user_info(
566549 let id_token = OidcToken :: from_str ( & cookie_value)
567550 . with_context ( || format ! ( "Invalid SQLPage auth cookie: {cookie_value:?}" ) ) ?;
568551
569- // Try to get state from cookies if this is a callback request
570- let state = if request. path ( ) == SQLPAGE_REDIRECT_URI {
571- if let Ok ( params ) = Query :: < OidcCallbackParams > :: from_query ( request. query_string ( ) ) {
572- get_state_from_cookies ( request, & params . state ) . ok ( )
552+ // Try to get nonce from cookies if this is a callback request
553+ let nonce = if request. path ( ) == SQLPAGE_REDIRECT_URI {
554+ if let Ok ( _params ) = Query :: < OidcCallbackParams > :: from_query ( request. query_string ( ) ) {
555+ get_nonce_from_cookie ( request) . ok ( )
573556 } else {
574557 None
575558 }
@@ -579,7 +562,7 @@ async fn get_authenticated_user_info(
579562
580563 log:: debug!( "Verifying id token: {id_token:?}" ) ;
581564 let claims = oidc_state
582- . get_token_claims ( id_token, state . as_ref ( ) )
565+ . get_token_claims ( id_token, nonce . as_ref ( ) )
583566 . await ?;
584567 log:: debug!( "The current user is: {claims:?}" ) ;
585568 Ok ( Some ( claims) )
@@ -731,7 +714,7 @@ fn make_oidc_client(
731714 Ok ( client)
732715}
733716
734- #[ derive( Debug , Deserialize ) ]
717+ #[ derive( Debug , Deserialize , Clone ) ]
735718struct OidcCallbackParams {
736719 code : String ,
737720 state : CsrfToken ,
@@ -783,13 +766,13 @@ fn hash_nonce(nonce: &Nonce) -> String {
783766
784767fn check_nonce (
785768 id_token_nonce : Option < & Nonce > ,
786- login_state : Option < & OidcLoginState > ,
769+ expected_nonce : Option < & Nonce > ,
787770) -> Result < ( ) , String > {
788- let Some ( state ) = login_state else {
789- return Ok ( ( ) ) ; // No login state , no nonce to check
771+ let Some ( expected ) = expected_nonce else {
772+ return Ok ( ( ) ) ; // No expected nonce , no validation needed
790773 } ;
791774 match id_token_nonce {
792- Some ( id_token_nonce) => nonce_matches ( id_token_nonce, & state . nonce ) ,
775+ Some ( id_token_nonce) => nonce_matches ( id_token_nonce, expected ) ,
793776 None => Err ( "No nonce found in the ID token" . to_string ( ) ) ,
794777 }
795778}
@@ -816,107 +799,78 @@ fn nonce_matches(id_token_nonce: &Nonce, state_nonce: &Nonce) -> Result<(), Stri
816799 Ok ( ( ) )
817800}
818801
819- #[ derive( Debug , Serialize , Deserialize ) ]
820- struct OidcLoginState {
821- /// The URL to redirect to after the login process is complete.
822- #[ serde( rename = "u" ) ]
823- initial_url : String ,
824- /// The CSRF token to use for the login process.
825- #[ serde( rename = "c" ) ]
826- csrf_token : CsrfToken ,
827- /// The source nonce to use for the login process. It must be checked against the hash
828- /// stored in the ID token.
829- #[ serde( rename = "n" ) ]
830- nonce : Nonce ,
831- }
832-
833802#[ derive( Debug , Serialize , Deserialize ) ]
834803struct OidcNonceState {
835- /// The source nonce to use for the login process. It must be checked against the hash
836- /// stored in the ID token.
837804 #[ serde( rename = "n" ) ]
838805 nonce : Nonce ,
839806}
840807
841808#[ derive( Debug , Serialize , Deserialize ) ]
842809struct OidcRedirectState {
843- /// The URL to redirect to after the login process is complete.
844810 #[ serde( rename = "u" ) ]
845811 initial_url : String ,
846812}
847813
848- impl OidcLoginState {
849- fn new ( initial_url : String , auth_url : AuthUrlParams ) -> Self {
850- Self {
851- initial_url,
852- csrf_token : auth_url. csrf_token ,
853- nonce : auth_url. nonce ,
854- }
855- }
856- }
857-
858- fn create_state_cookies ( login_state : & OidcLoginState ) -> ( Cookie < ' _ > , Cookie < ' _ > ) {
859- // Create nonce cookie (longer-lived for security)
814+ fn create_nonce_cookie ( nonce : & Nonce ) -> Cookie < ' _ > {
860815 let nonce_state = OidcNonceState {
861- nonce : login_state . nonce . clone ( ) ,
816+ nonce : nonce. clone ( ) ,
862817 } ;
863818 let nonce_json = serde_json:: to_string ( & nonce_state) . unwrap ( ) ;
864- let nonce_cookie = Cookie :: build ( SQLPAGE_NONCE_COOKIE_NAME , nonce_json)
819+ Cookie :: build ( SQLPAGE_NONCE_COOKIE_NAME , nonce_json)
865820 . secure ( true )
866821 . http_only ( true )
867822 . same_site ( actix_web:: cookie:: SameSite :: Lax )
868823 . path ( "/" )
869- . max_age ( actix_web:: cookie:: time:: Duration :: minutes ( 10 ) ) // 10 minutes should be enough for login flow
870- . finish ( ) ;
824+ . max_age ( actix_web:: cookie:: time:: Duration :: minutes ( 10 ) )
825+ . finish ( )
826+ }
871827
872- // Create state-specific redirect cookie (short-lived)
828+ fn create_redirect_cookie ( csrf_token : & CsrfToken , initial_url : & str ) -> Cookie < ' static > {
873829 let redirect_state = OidcRedirectState {
874- initial_url : login_state . initial_url . clone ( ) ,
830+ initial_url : initial_url. to_string ( ) ,
875831 } ;
876832 let redirect_json = serde_json:: to_string ( & redirect_state) . unwrap ( ) ;
877- let state_cookie_name = format ! (
878- "{}{}" ,
879- SQLPAGE_STATE_COOKIE_PREFIX ,
880- login_state. csrf_token. secret( )
881- ) ;
882- let redirect_cookie = Cookie :: build ( state_cookie_name, redirect_json)
833+ let cookie_name = format ! ( "{}{}" , SQLPAGE_STATE_COOKIE_PREFIX , csrf_token. secret( ) ) ;
834+ Cookie :: build ( cookie_name, redirect_json)
883835 . secure ( true )
884836 . http_only ( true )
885837 . same_site ( actix_web:: cookie:: SameSite :: Lax )
886838 . path ( "/" )
887- . max_age ( actix_web:: cookie:: time:: Duration :: minutes ( 5 ) ) // Short-lived, only needed during login flow
888- . finish ( ) ;
839+ . max_age ( actix_web:: cookie:: time:: Duration :: minutes ( 5 ) )
840+ . finish ( )
841+ }
889842
890- ( nonce_cookie, redirect_cookie)
843+ fn create_cleanup_cookie ( csrf_token : & CsrfToken ) -> Cookie < ' _ > {
844+ let cookie_name = format ! ( "{}{}" , SQLPAGE_STATE_COOKIE_PREFIX , csrf_token. secret( ) ) ;
845+ Cookie :: build ( cookie_name, "" )
846+ . secure ( true )
847+ . http_only ( true )
848+ . same_site ( actix_web:: cookie:: SameSite :: Lax )
849+ . path ( "/" )
850+ . max_age ( actix_web:: cookie:: time:: Duration :: seconds ( 0 ) )
851+ . finish ( )
852+ }
853+
854+ fn get_nonce_from_cookie ( request : & ServiceRequest ) -> anyhow:: Result < Nonce > {
855+ let cookie = request
856+ . cookie ( SQLPAGE_NONCE_COOKIE_NAME )
857+ . with_context ( || format ! ( "No {SQLPAGE_NONCE_COOKIE_NAME} cookie found" ) ) ?;
858+ let nonce_state: OidcNonceState = serde_json:: from_str ( cookie. value ( ) )
859+ . with_context ( || format ! ( "Failed to parse nonce from cookie: {cookie}" ) ) ?;
860+ Ok ( nonce_state. nonce )
891861}
892862
893- fn get_state_from_cookies (
863+ fn get_redirect_url_from_cookie (
894864 request : & ServiceRequest ,
895865 csrf_token : & CsrfToken ,
896- ) -> anyhow:: Result < OidcLoginState > {
897- // Get nonce from the nonce cookie
898- let nonce_cookie = request. cookie ( SQLPAGE_NONCE_COOKIE_NAME ) . with_context ( || {
899- format ! ( "No {SQLPAGE_NONCE_COOKIE_NAME} cookie found for {SQLPAGE_REDIRECT_URI}" )
900- } ) ?;
901- let nonce_state: OidcNonceState = serde_json:: from_str ( nonce_cookie. value ( ) )
902- . with_context ( || format ! ( "Failed to parse OIDC nonce from cookie: {nonce_cookie}" ) ) ?;
903-
904- // Get redirect URL from the state-specific cookie
905- let state_cookie_name = format ! ( "{}{}" , SQLPAGE_STATE_COOKIE_PREFIX , csrf_token. secret( ) ) ;
906- let redirect_cookie = request. cookie ( & state_cookie_name) . with_context ( || {
907- format ! ( "No {state_cookie_name} cookie found for {SQLPAGE_REDIRECT_URI}" )
908- } ) ?;
909- let redirect_state: OidcRedirectState = serde_json:: from_str ( redirect_cookie. value ( ) )
910- . with_context ( || {
911- format ! ( "Failed to parse OIDC redirect state from cookie: {redirect_cookie}" )
912- } ) ?;
913-
914- // Reconstruct the full login state
915- Ok ( OidcLoginState {
916- initial_url : redirect_state. initial_url ,
917- csrf_token : csrf_token. clone ( ) ,
918- nonce : nonce_state. nonce ,
919- } )
866+ ) -> anyhow:: Result < String > {
867+ let cookie_name = format ! ( "{}{}" , SQLPAGE_STATE_COOKIE_PREFIX , csrf_token. secret( ) ) ;
868+ let cookie = request
869+ . cookie ( & cookie_name)
870+ . with_context ( || format ! ( "No {cookie_name} cookie found" ) ) ?;
871+ let redirect_state: OidcRedirectState = serde_json:: from_str ( cookie. value ( ) )
872+ . with_context ( || format ! ( "Failed to parse redirect state from cookie: {cookie}" ) ) ?;
873+ Ok ( redirect_state. initial_url )
920874}
921875
922876/// Given an audience, verify if it is trusted. The `client_id` is always trusted, independently of this function.
0 commit comments