Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/data-model/src/upstream_oauth2/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ pub struct UpstreamOAuthAuthorizationSession {
pub provider_id: Ulid,
pub state_str: String,
pub code_challenge_verifier: Option<String>,
pub nonce: String,
pub nonce: Option<String>,
pub created_at: DateTime<Utc>,
}

Expand Down
9 changes: 1 addition & 8 deletions crates/handlers/src/admin/v1/upstream_oauth_links/delete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,7 @@ mod tests {
// Pretend it was linked by an authorization session
let session = repo
.upstream_oauth_session()
.add(
&mut rng,
&state.clock,
&provider,
String::new(),
None,
String::new(),
)
.add(&mut rng, &state.clock, &provider, String::new(), None, None)
.await
.unwrap();

Expand Down
10 changes: 6 additions & 4 deletions crates/handlers/src/upstream_oauth2/callback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,10 +356,12 @@ pub(crate) async fn handler(
)
.map_err(mas_oidc_client::error::IdTokenError::from)?;

// Nonce must match.
mas_jose::claims::NONCE
.extract_required_with_options(&mut claims, session.nonce.as_str())
.map_err(mas_oidc_client::error::IdTokenError::from)?;
// Nonce must match if present.
if let Some(nonce) = session.nonce.as_deref() {
mas_jose::claims::NONCE
.extract_required_with_options(&mut claims, nonce)
.map_err(mas_oidc_client::error::IdTokenError::from)?;
}

context = context.with_id_token_claims(claims);
}
Expand Down
2 changes: 1 addition & 1 deletion crates/handlers/src/upstream_oauth2/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -998,7 +998,7 @@ mod tests {
&provider,
"state".to_owned(),
None,
"nonce".to_owned(),
None,
)
.await
.unwrap();
Expand Down
22 changes: 14 additions & 8 deletions crates/oidc-client/src/requests/authorization_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ pub struct AuthorizationValidationData {
pub state: String,

/// A string to mitigate replay attacks.
pub nonce: String,
pub nonce: Option<String>,

/// The URI where the end-user will be redirected after authorization.
pub redirect_uri: Url,
Expand All @@ -216,7 +216,7 @@ fn build_authorization_request(
) -> Result<(FullAuthorizationRequest, AuthorizationValidationData), AuthorizationError> {
let AuthorizationRequestData {
client_id,
mut scope,
scope,
redirect_uri,
code_challenge_methods_supported,
display,
Expand All @@ -229,9 +229,13 @@ fn build_authorization_request(
response_mode,
} = authorization_data;

let is_openid = scope.contains(&OPENID);

// Generate a random CSRF "state" token and a nonce.
let state = Alphanumeric.sample_string(rng, 16);
let nonce = Alphanumeric.sample_string(rng, 16);

// Generate a random nonce if we're in 'OpenID Connect' mode
let nonce = is_openid.then(|| Alphanumeric.sample_string(rng, 16));

// Use PKCE, whenever possible.
let (pkce, code_challenge_verifier) = if code_challenge_methods_supported
Expand Down Expand Up @@ -263,7 +267,7 @@ fn build_authorization_request(
scope,
state: Some(state.clone()),
response_mode,
nonce: Some(nonce.clone()),
nonce: nonce.clone(),
display,
prompt,
max_age,
Expand Down Expand Up @@ -440,10 +444,12 @@ pub async fn access_token_with_authorization_code(
.extract_optional_with_options(&mut claims, TokenHash::new(signing_alg, &code))
.map_err(IdTokenError::from)?;

// Nonce must match.
claims::NONCE
.extract_required_with_options(&mut claims, validation_data.nonce.as_str())
.map_err(IdTokenError::from)?;
// Nonce must match if we have one.
if let Some(nonce) = validation_data.nonce.as_deref() {
claims::NONCE
.extract_required_with_options(&mut claims, nonce)
.map_err(IdTokenError::from)?;
}

Some(id_token.into_owned())
} else {
Expand Down
6 changes: 3 additions & 3 deletions crates/oidc-client/tests/it/requests/authorization_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ async fn pass_access_token_with_authorization_code() {
let redirect_uri = Url::parse(REDIRECT_URI).unwrap();
let validation_data = AuthorizationValidationData {
state: "some_state".to_owned(),
nonce: NONCE.to_owned(),
nonce: Some(NONCE.to_owned()),
redirect_uri,
code_challenge_verifier: Some(CODE_VERIFIER.to_owned()),
};
Expand Down Expand Up @@ -244,7 +244,7 @@ async fn fail_access_token_with_authorization_code_wrong_nonce() {
let redirect_uri = Url::parse(REDIRECT_URI).unwrap();
let validation_data = AuthorizationValidationData {
state: "some_state".to_owned(),
nonce: "wrong_nonce".to_owned(),
nonce: Some("wrong_nonce".to_owned()),
redirect_uri,
code_challenge_verifier: Some(CODE_VERIFIER.to_owned()),
};
Expand Down Expand Up @@ -306,7 +306,7 @@ async fn fail_access_token_with_authorization_code_no_id_token() {
let nonce = "some_nonce".to_owned();
let validation_data = AuthorizationValidationData {
state: "some_state".to_owned(),
nonce: nonce.clone(),
nonce: Some(nonce.clone()),
redirect_uri,
code_challenge_verifier: Some(CODE_VERIFIER.to_owned()),
};
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- Copyright 2025 New Vector Ltd.
--
-- SPDX-License-Identifier: AGPL-3.0-only
-- Please see LICENSE in the repository root for full details.

-- Make the nonce column optional on the upstream oauth sessions
ALTER TABLE "upstream_oauth_authorization_sessions"
ALTER COLUMN "nonce" DROP NOT NULL;
2 changes: 1 addition & 1 deletion crates/storage-pg/src/upstream_oauth2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ mod tests {
&provider,
"some-state".to_owned(),
None,
"some-nonce".to_owned(),
Some("some-nonce".to_owned()),
)
.await
.unwrap();
Expand Down
4 changes: 2 additions & 2 deletions crates/storage-pg/src/upstream_oauth2/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ struct SessionLookup {
upstream_oauth_link_id: Option<Uuid>,
state: String,
code_challenge_verifier: Option<String>,
nonce: String,
nonce: Option<String>,
id_token: Option<String>,
userinfo: Option<serde_json::Value>,
created_at: DateTime<Utc>,
Expand Down Expand Up @@ -191,7 +191,7 @@ impl UpstreamOAuthSessionRepository for PgUpstreamOAuthSessionRepository<'_> {
upstream_oauth_provider: &UpstreamOAuthProvider,
state_str: String,
code_challenge_verifier: Option<String>,
nonce: String,
nonce: Option<String>,
) -> Result<UpstreamOAuthAuthorizationSession, Self::Error> {
let created_at = clock.now();
let id = Ulid::from_datetime_with_source(created_at.into(), rng);
Expand Down
6 changes: 3 additions & 3 deletions crates/storage/src/upstream_oauth2/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ pub trait UpstreamOAuthSessionRepository: Send + Sync {
/// upstream OAuth provider
/// * `code_challenge_verifier`: the code challenge verifier used in this
/// session, if PKCE is being used
/// * `nonce`: the `nonce` used in this session
/// * `nonce`: the `nonce` used in this session if in OIDC mode
///
/// # Errors
///
Expand All @@ -60,7 +60,7 @@ pub trait UpstreamOAuthSessionRepository: Send + Sync {
upstream_oauth_provider: &UpstreamOAuthProvider,
state: String,
code_challenge_verifier: Option<String>,
nonce: String,
nonce: Option<String>,
) -> Result<UpstreamOAuthAuthorizationSession, Self::Error>;

/// Mark a session as completed and associate the given link
Expand Down Expand Up @@ -122,7 +122,7 @@ repository_impl!(UpstreamOAuthSessionRepository:
upstream_oauth_provider: &UpstreamOAuthProvider,
state: String,
code_challenge_verifier: Option<String>,
nonce: String,
nonce: Option<String>,
) -> Result<UpstreamOAuthAuthorizationSession, Self::Error>;

async fn complete_with_link(
Expand Down