Skip to content

Commit a8a9c2a

Browse files
committed
Refactor OIDC state management to use a temporary login flow state cookie, enhancing nonce and redirect handling. Update cookie creation and retrieval methods for improved clarity and maintainability.
fixes #1014
1 parent ad82461 commit a8a9c2a

File tree

1 file changed

+46
-23
lines changed

1 file changed

+46
-23
lines changed

src/webserver/oidc.rs

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,13 @@ type LocalBoxFuture<T> = Pin<Box<dyn Future<Output = T> + 'static>>;
4141
const SQLPAGE_AUTH_COOKIE_NAME: &str = "sqlpage_auth";
4242
const SQLPAGE_REDIRECT_URI: &str = "/sqlpage/oidc_callback";
4343
const SQLPAGE_NONCE_COOKIE_NAME: &str = "sqlpage_oidc_nonce";
44-
const SQLPAGE_REDIRECT_URL_COOKIE_PREFIX: &str = "sqlpage_oidc_redirect_url_";
44+
const SQLPAGE_TMP_LOGIN_STATE_COOKIE_PREFIX: &str = "sqlpage_oidc_state_";
4545
const OIDC_CLIENT_MAX_REFRESH_INTERVAL: Duration = Duration::from_secs(60 * 60);
4646
const OIDC_CLIENT_MIN_REFRESH_INTERVAL: Duration = Duration::from_secs(5);
4747
const AUTH_COOKIE_EXPIRATION: awc::cookie::time::Duration =
4848
actix_web::cookie::time::Duration::days(7);
49+
const LOGIN_FLOW_STATE_COOKIE_EXPIRATION: awc::cookie::time::Duration =
50+
actix_web::cookie::time::Duration::minutes(10);
4951

5052
#[derive(Clone, Debug, Serialize, Deserialize)]
5153
#[serde(transparent)]
@@ -413,26 +415,29 @@ async fn process_oidc_callback(
413415
.with_context(|| format!("{SQLPAGE_REDIRECT_URI}: invalid url parameters"))?
414416
.into_inner();
415417
log::debug!("Processing OIDC callback with params: {params:?}. Requesting token...");
416-
let mut redirect_url_cookie = get_redirect_url_cookie(request, &params.state)?;
418+
let mut tmp_login_flow_state_cookie = get_tmp_login_flow_state_cookie(request, &params.state)?;
417419
let client = oidc_state.get_client().await;
418420
let http_client = get_http_client_from_appdata(request)?;
419421
let id_token = exchange_code_for_token(&client, http_client, params.clone()).await?;
420422
log::debug!("Received token response: {id_token:?}");
421-
let nonce = get_nonce_from_cookie(request)?;
422-
let redirect_target = validate_redirect_url(redirect_url_cookie.value().to_string());
423+
let LoginFlowState {
424+
nonce,
425+
redirect_target,
426+
} = parse_login_flow_state(&tmp_login_flow_state_cookie)?;
427+
let redirect_target = validate_redirect_url(redirect_target.to_string());
423428

424429
log::info!("Redirecting to {redirect_target} after a successful login");
425430
let mut response = build_redirect_response(redirect_target);
426-
set_auth_cookie(&mut response, &id_token).context("Failed to set auth cookie")?;
431+
set_auth_cookie(&mut response, &id_token);
427432
let claims = oidc_state
428433
.get_token_claims(id_token, &nonce)
429434
.await
430435
.context("The identity provider returned an invalid ID token")?;
431436
log::debug!("{} successfully logged in", claims.subject().as_str());
432-
let nonce_cookie = create_nonce_cookie(&nonce);
437+
let nonce_cookie = create_final_nonce_cookie(&nonce);
433438
response.add_cookie(&nonce_cookie)?;
434-
redirect_url_cookie.set_path("/"); // Required to clean up the cookie
435-
response.add_removal_cookie(&redirect_url_cookie)?;
439+
tmp_login_flow_state_cookie.set_path("/"); // Required to clean up the cookie
440+
response.add_removal_cookie(&tmp_login_flow_state_cookie)?;
436441
Ok(response)
437442
}
438443

@@ -456,7 +461,7 @@ async fn exchange_code_for_token(
456461
Ok(id_token.clone())
457462
}
458463

459-
fn set_auth_cookie(response: &mut HttpResponse, id_token: &OidcToken) -> anyhow::Result<()> {
464+
fn set_auth_cookie(response: &mut HttpResponse, id_token: &OidcToken) {
460465
let id_token_str = id_token.to_string();
461466
log::trace!("Setting auth cookie: {SQLPAGE_AUTH_COOKIE_NAME}=\"{id_token_str}\"");
462467
let id_token_size_kb = id_token_str.len() / 1024;
@@ -475,20 +480,17 @@ fn set_auth_cookie(response: &mut HttpResponse, id_token: &OidcToken) -> anyhow:
475480
.finish();
476481

477482
response.add_cookie(&cookie).unwrap();
478-
Ok(())
479483
}
480484

481485
async fn build_auth_provider_redirect_response(
482486
oidc_state: &OidcState,
483487
initial_url: &str,
484488
) -> HttpResponse {
485489
let AuthUrl { url, params } = build_auth_url(oidc_state).await;
486-
let nonce_cookie = create_nonce_cookie(&params.nonce);
487-
let redirect_cookie = create_redirect_cookie(&params.csrf_token, initial_url);
490+
let tmp_login_flow_state_cookie = create_tmp_login_flow_state_cookie(&params, initial_url);
488491
HttpResponse::TemporaryRedirect()
489492
.append_header((header::LOCATION, url.to_string()))
490-
.cookie(nonce_cookie)
491-
.cookie(redirect_cookie)
493+
.cookie(tmp_login_flow_state_cookie)
492494
.body("Redirecting...")
493495
}
494496

@@ -510,7 +512,7 @@ async fn get_authenticated_user_info(
510512
let id_token = OidcToken::from_str(&cookie_value)
511513
.with_context(|| format!("Invalid SQLPage auth cookie: {cookie_value:?}"))?;
512514

513-
let nonce = get_nonce_from_cookie(request)?;
515+
let nonce = get_final_nonce_from_cookie(request)?;
514516
log::debug!("Verifying id token: {id_token:?}");
515517
let claims = oidc_state.get_token_claims(id_token, &nonce).await?;
516518
log::debug!("The current user is: {claims:?}");
@@ -742,7 +744,7 @@ fn nonce_matches(id_token_nonce: &Nonce, state_nonce: &Nonce) -> Result<(), Stri
742744
Ok(())
743745
}
744746

745-
fn create_nonce_cookie(nonce: &Nonce) -> Cookie<'_> {
747+
fn create_final_nonce_cookie(nonce: &Nonce) -> Cookie<'_> {
746748
Cookie::build(SQLPAGE_NONCE_COOKIE_NAME, nonce.secret())
747749
.secure(true)
748750
.http_only(true)
@@ -752,34 +754,55 @@ fn create_nonce_cookie(nonce: &Nonce) -> Cookie<'_> {
752754
.finish()
753755
}
754756

755-
fn create_redirect_cookie<'a>(csrf_token: &CsrfToken, initial_url: &'a str) -> Cookie<'a> {
756-
let cookie_name = SQLPAGE_REDIRECT_URL_COOKIE_PREFIX.to_owned() + csrf_token.secret();
757-
Cookie::build(cookie_name, initial_url)
757+
fn create_tmp_login_flow_state_cookie<'a>(
758+
params: &'a AuthUrlParams,
759+
initial_url: &'a str,
760+
) -> Cookie<'a> {
761+
let csrf_token = &params.csrf_token;
762+
let cookie_name = SQLPAGE_TMP_LOGIN_STATE_COOKIE_PREFIX.to_owned() + csrf_token.secret();
763+
let cookie_value = serde_json::to_string(&LoginFlowState {
764+
nonce: params.nonce.clone(),
765+
redirect_target: initial_url,
766+
})
767+
.expect("login flow state is always serializable");
768+
Cookie::build(cookie_name, cookie_value)
758769
.secure(true)
759770
.http_only(true)
760771
.same_site(actix_web::cookie::SameSite::Lax)
761772
.path("/")
762-
.max_age(actix_web::cookie::time::Duration::minutes(10))
773+
.max_age(LOGIN_FLOW_STATE_COOKIE_EXPIRATION)
763774
.finish()
764775
}
765776

766-
fn get_nonce_from_cookie(request: &ServiceRequest) -> anyhow::Result<Nonce> {
777+
fn get_final_nonce_from_cookie(request: &ServiceRequest) -> anyhow::Result<Nonce> {
767778
let cookie = request
768779
.cookie(SQLPAGE_NONCE_COOKIE_NAME)
769780
.with_context(|| format!("No {SQLPAGE_NONCE_COOKIE_NAME} cookie found"))?;
770781
Ok(Nonce::new(cookie.value().to_string()))
771782
}
772783

773-
fn get_redirect_url_cookie(
784+
fn get_tmp_login_flow_state_cookie(
774785
request: &ServiceRequest,
775786
csrf_token: &CsrfToken,
776787
) -> anyhow::Result<Cookie<'static>> {
777-
let cookie_name = SQLPAGE_REDIRECT_URL_COOKIE_PREFIX.to_owned() + csrf_token.secret();
788+
let cookie_name = SQLPAGE_TMP_LOGIN_STATE_COOKIE_PREFIX.to_owned() + csrf_token.secret();
778789
request
779790
.cookie(&cookie_name)
780791
.with_context(|| format!("No {cookie_name} cookie found"))
781792
}
782793

794+
#[derive(Debug, Serialize, Deserialize, Clone)]
795+
struct LoginFlowState<'a> {
796+
#[serde(rename = "n")]
797+
nonce: Nonce,
798+
#[serde(rename = "r")]
799+
redirect_target: &'a str,
800+
}
801+
802+
fn parse_login_flow_state<'a>(cookie: &'a Cookie<'_>) -> anyhow::Result<LoginFlowState<'a>> {
803+
Ok(serde_json::from_str(cookie.value())?)
804+
}
805+
783806
/// Given an audience, verify if it is trusted. The `client_id` is always trusted, independently of this function.
784807
#[derive(Clone, Debug)]
785808
pub struct AudienceVerifier(Option<HashSet<String>>);

0 commit comments

Comments
 (0)