From 2242f70a1a46fd06d2087ce4bb129c79204b3c89 Mon Sep 17 00:00:00 2001 From: psdimon <65540838+psdimon@users.noreply.github.com> Date: Wed, 5 Aug 2026 17:16:50 +0300 Subject: [PATCH] fix(oauth): preserve RFC 9207 iss from MCP OAuth callback (#10678) Co-authored-by: d --- crates/goose/src/agents/extension_manager.rs | 8 ++++- crates/goose/src/oauth/mod.rs | 32 +++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/crates/goose/src/agents/extension_manager.rs b/crates/goose/src/agents/extension_manager.rs index 79c47b111..93cfa8c04 100644 --- a/crates/goose/src/agents/extension_manager.rs +++ b/crates/goose/src/agents/extension_manager.rs @@ -805,7 +805,13 @@ async fn create_streamable_http_client( ) .await } - Err(_) => Ok(Box::new(client_res?)), + Err(e) => { + warn!( + "[OAuth:{}] Browser authorization flow failed: {:#}", + name, e + ); + Ok(Box::new(client_res?)) + } } } else { Ok(Box::new(client_res?)) diff --git a/crates/goose/src/oauth/mod.rs b/crates/goose/src/oauth/mod.rs index 0984b4c60..8e5c3289c 100644 --- a/crates/goose/src/oauth/mod.rs +++ b/crates/goose/src/oauth/mod.rs @@ -31,6 +31,7 @@ struct AppState { struct CallbackParams { code: String, state: String, + iss: Option, } fn resolve_oauth_callback_timeout(value: Option<&str>) -> Duration { @@ -173,8 +174,11 @@ pub async fn oauth_flow( let CallbackParams { code: auth_code, state: csrf_token, + iss, } = callback_params?; - oauth_state.handle_callback(&auth_code, &csrf_token).await?; + oauth_state + .handle_callback_with_issuer(&auth_code, &csrf_token, iss.as_deref()) + .await?; let (client_id, token_response) = oauth_state.get_credentials().await?; @@ -242,6 +246,7 @@ mod tests { .send(CallbackParams { code: "auth-code".to_string(), state: "csrf-state".to_string(), + iss: Some("https://auth.example".to_string()), }) .unwrap(); @@ -256,6 +261,31 @@ mod tests { assert_eq!(params.code, "auth-code"); assert_eq!(params.state, "csrf-state"); + assert_eq!(params.iss.as_deref(), Some("https://auth.example")); + } + + #[test] + fn callback_params_capture_rfc_9207_issuer() { + let uri: axum::http::Uri = + "http://127.0.0.1/oauth_callback?code=auth-code&state=csrf-state&iss=https%3A%2F%2Fauth.example%2Fidp" + .parse() + .unwrap(); + + let Query(params) = Query::::try_from_uri(&uri).unwrap(); + + assert_eq!(params.iss.as_deref(), Some("https://auth.example/idp")); + } + + #[test] + fn callback_params_accept_missing_issuer() { + let uri: axum::http::Uri = + "http://127.0.0.1/oauth_callback?code=auth-code&state=csrf-state" + .parse() + .unwrap(); + + let Query(params) = Query::::try_from_uri(&uri).unwrap(); + + assert_eq!(params.iss, None); } #[tokio::test]