Skip to content

Commit 5d58d9d

Browse files
committed
Don't generate and send a nonce for non-OIDC-compliant auth requests
1 parent 6dfd60b commit 5d58d9d

File tree

11 files changed

+41
-32
lines changed

11 files changed

+41
-32
lines changed

crates/data-model/src/upstream_oauth2/session.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ pub struct UpstreamOAuthAuthorizationSession {
250250
pub provider_id: Ulid,
251251
pub state_str: String,
252252
pub code_challenge_verifier: Option<String>,
253-
pub nonce: String,
253+
pub nonce: Option<String>,
254254
pub created_at: DateTime<Utc>,
255255
}
256256

crates/handlers/src/admin/v1/upstream_oauth_links/delete.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,7 @@ mod tests {
108108
// Pretend it was linked by an authorization session
109109
let session = repo
110110
.upstream_oauth_session()
111-
.add(
112-
&mut rng,
113-
&state.clock,
114-
&provider,
115-
String::new(),
116-
None,
117-
String::new(),
118-
)
111+
.add(&mut rng, &state.clock, &provider, String::new(), None, None)
119112
.await
120113
.unwrap();
121114

crates/handlers/src/upstream_oauth2/callback.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -356,10 +356,12 @@ pub(crate) async fn handler(
356356
)
357357
.map_err(mas_oidc_client::error::IdTokenError::from)?;
358358

359-
// Nonce must match.
360-
mas_jose::claims::NONCE
361-
.extract_required_with_options(&mut claims, session.nonce.as_str())
362-
.map_err(mas_oidc_client::error::IdTokenError::from)?;
359+
// Nonce must match if present.
360+
if let Some(nonce) = session.nonce.as_deref() {
361+
mas_jose::claims::NONCE
362+
.extract_required_with_options(&mut claims, nonce)
363+
.map_err(mas_oidc_client::error::IdTokenError::from)?;
364+
}
363365

364366
context = context.with_id_token_claims(claims);
365367
}

crates/handlers/src/upstream_oauth2/link.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -998,7 +998,7 @@ mod tests {
998998
&provider,
999999
"state".to_owned(),
10001000
None,
1001-
"nonce".to_owned(),
1001+
None,
10021002
)
10031003
.await
10041004
.unwrap();

crates/oidc-client/src/requests/authorization_code.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ pub struct AuthorizationValidationData {
191191
pub state: String,
192192

193193
/// A string to mitigate replay attacks.
194-
pub nonce: String,
194+
pub nonce: Option<String>,
195195

196196
/// The URI where the end-user will be redirected after authorization.
197197
pub redirect_uri: Url,
@@ -216,7 +216,7 @@ fn build_authorization_request(
216216
) -> Result<(FullAuthorizationRequest, AuthorizationValidationData), AuthorizationError> {
217217
let AuthorizationRequestData {
218218
client_id,
219-
mut scope,
219+
scope,
220220
redirect_uri,
221221
code_challenge_methods_supported,
222222
display,
@@ -229,9 +229,13 @@ fn build_authorization_request(
229229
response_mode,
230230
} = authorization_data;
231231

232+
let is_openid = scope.contains(&OPENID);
233+
232234
// Generate a random CSRF "state" token and a nonce.
233235
let state = Alphanumeric.sample_string(rng, 16);
234-
let nonce = Alphanumeric.sample_string(rng, 16);
236+
237+
// Generate a random nonce if we're in 'OpenID Connect' mode
238+
let nonce = is_openid.then(|| Alphanumeric.sample_string(rng, 16));
235239

236240
// Use PKCE, whenever possible.
237241
let (pkce, code_challenge_verifier) = if code_challenge_methods_supported
@@ -263,7 +267,7 @@ fn build_authorization_request(
263267
scope,
264268
state: Some(state.clone()),
265269
response_mode,
266-
nonce: Some(nonce.clone()),
270+
nonce: nonce.clone(),
267271
display,
268272
prompt,
269273
max_age,
@@ -440,10 +444,12 @@ pub async fn access_token_with_authorization_code(
440444
.extract_optional_with_options(&mut claims, TokenHash::new(signing_alg, &code))
441445
.map_err(IdTokenError::from)?;
442446

443-
// Nonce must match.
444-
claims::NONCE
445-
.extract_required_with_options(&mut claims, validation_data.nonce.as_str())
446-
.map_err(IdTokenError::from)?;
447+
// Nonce must match if we have one.
448+
if let Some(nonce) = validation_data.nonce.as_deref() {
449+
claims::NONCE
450+
.extract_required_with_options(&mut claims, nonce)
451+
.map_err(IdTokenError::from)?;
452+
}
447453

448454
Some(id_token.into_owned())
449455
} else {

crates/oidc-client/tests/it/requests/authorization_code.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ async fn pass_access_token_with_authorization_code() {
186186
let redirect_uri = Url::parse(REDIRECT_URI).unwrap();
187187
let validation_data = AuthorizationValidationData {
188188
state: "some_state".to_owned(),
189-
nonce: NONCE.to_owned(),
189+
nonce: Some(NONCE.to_owned()),
190190
redirect_uri,
191191
code_challenge_verifier: Some(CODE_VERIFIER.to_owned()),
192192
};
@@ -244,7 +244,7 @@ async fn fail_access_token_with_authorization_code_wrong_nonce() {
244244
let redirect_uri = Url::parse(REDIRECT_URI).unwrap();
245245
let validation_data = AuthorizationValidationData {
246246
state: "some_state".to_owned(),
247-
nonce: "wrong_nonce".to_owned(),
247+
nonce: Some("wrong_nonce".to_owned()),
248248
redirect_uri,
249249
code_challenge_verifier: Some(CODE_VERIFIER.to_owned()),
250250
};
@@ -306,7 +306,7 @@ async fn fail_access_token_with_authorization_code_no_id_token() {
306306
let nonce = "some_nonce".to_owned();
307307
let validation_data = AuthorizationValidationData {
308308
state: "some_state".to_owned(),
309-
nonce: nonce.clone(),
309+
nonce: Some(nonce.clone()),
310310
redirect_uri,
311311
code_challenge_verifier: Some(CODE_VERIFIER.to_owned()),
312312
};

crates/storage-pg/.sqlx/query-37a124678323380357fa9d1375fd125fb35476ac3008e5adbd04a761d5edcd42.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- Copyright 2025 New Vector Ltd.
2+
--
3+
-- SPDX-License-Identifier: AGPL-3.0-only
4+
-- Please see LICENSE in the repository root for full details.
5+
6+
-- Make the nonce column optional on the upstream oauth sessions
7+
ALTER TABLE "upstream_oauth_authorization_sessions"
8+
ALTER COLUMN "nonce" DROP NOT NULL;

crates/storage-pg/src/upstream_oauth2/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ mod tests {
108108
&provider,
109109
"some-state".to_owned(),
110110
None,
111-
"some-nonce".to_owned(),
111+
Some("some-nonce".to_owned()),
112112
)
113113
.await
114114
.unwrap();

crates/storage-pg/src/upstream_oauth2/session.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ struct SessionLookup {
3838
upstream_oauth_link_id: Option<Uuid>,
3939
state: String,
4040
code_challenge_verifier: Option<String>,
41-
nonce: String,
41+
nonce: Option<String>,
4242
id_token: Option<String>,
4343
userinfo: Option<serde_json::Value>,
4444
created_at: DateTime<Utc>,
@@ -191,7 +191,7 @@ impl UpstreamOAuthSessionRepository for PgUpstreamOAuthSessionRepository<'_> {
191191
upstream_oauth_provider: &UpstreamOAuthProvider,
192192
state_str: String,
193193
code_challenge_verifier: Option<String>,
194-
nonce: String,
194+
nonce: Option<String>,
195195
) -> Result<UpstreamOAuthAuthorizationSession, Self::Error> {
196196
let created_at = clock.now();
197197
let id = Ulid::from_datetime_with_source(created_at.into(), rng);

0 commit comments

Comments
 (0)