Support confidential OAuth clients for enterprise-managed MCP auth (#56001)

## Why

Enterprise-managed MCP authentication previously required public OAuth clients at both the identity provider and the MCP authorization server, preventing use of registrations that require client secrets.

## What changed

- Accept separate `client_secret` and `token_endpoint_auth_method` settings for the enterprise IdP and MCP server OAuth registration. Support `client_secret_basic` and `client_secret_post`, defaulting to Basic when a secret is configured.
- Validate the registered method against server metadata and use it for enterprise login and both ID-JAG exchanges.
- Require a configured `oauth.authorization_server_issuer` for confidential MCP registrations and reject empty secrets or authentication methods without secrets.
- Require the account OAuth client to match the configured IdP client before reusing an account grant with a provisioned secret; otherwise use dedicated enterprise login.

## Testing

Extend configuration, login, exchange, and app-server lifecycle tests to cover confidential registrations, separate IdP and resource credentials, authentication method validation, and exclusion of client secrets from authorization URLs, stored login grants, and logs.

GitOrigin-RevId: b3be378c2b1bb67e6a3652c648e72fd2e21e956d
This commit is contained in:
jif
2026-10-21 22:30:35 +00:00
committed by copyberry
parent f496b09bd1
commit 6203846fb3
25 changed files with 627 additions and 90 deletions
+29 -1
View File
@@ -2492,6 +2492,14 @@
},
"type": "object"
},
"McpEmaTokenEndpointAuthMethod": {
"description": "Token endpoint authentication selected by a confidential EMA client registration.",
"enum": [
"client_secret_basic",
"client_secret_post"
],
"type": "string"
},
"McpEnterpriseManagedAuthConfig": {
"additionalProperties": false,
"properties": {
@@ -2561,12 +2569,24 @@
"additionalProperties": false,
"properties": {
"client_id": {
"description": "Public OAuth client registered with that enterprise IdP.",
"description": "OAuth client registered with that enterprise IdP.",
"type": "string"
},
"client_secret": {
"description": "Provisioned client secret for confidential IdP login and token exchange. Omitted for a public client.",
"type": "string"
},
"issuer": {
"description": "Issuer used for enterprise OAuth discovery and identity validation.",
"type": "string"
},
"token_endpoint_auth_method": {
"allOf": [
{
"$ref": "#/definitions/McpEmaTokenEndpointAuthMethod"
}
],
"description": "Registered EMA client authentication method. Defaults to client_secret_basic when a secret is configured; requires a client secret."
}
},
"required": [
@@ -2600,6 +2620,14 @@
"client_secret": {
"description": "OAuth client secret used for token exchange with a pre-registered client.",
"type": "string"
},
"token_endpoint_auth_method": {
"allOf": [
{
"$ref": "#/definitions/McpEmaTokenEndpointAuthMethod"
}
],
"description": "Registered EMA client authentication method. Defaults to client_secret_basic when a secret is configured; requires a client secret."
}
},
"type": "object"
@@ -169,6 +169,9 @@ fn serialize_mcp_server_table(config: &McpServerConfig) -> anyhow::Result<TomlTa
if let Some(client_secret) = &oauth.client_secret {
oauth_table["client_secret"] = value(client_secret.as_str());
}
if let Some(method) = oauth.token_endpoint_auth_method {
oauth_table["token_endpoint_auth_method"] = value(method.as_str());
}
if let Some(callback_url) = &oauth.callback_url {
oauth_table["callback_url"] = value(callback_url.clone());
}
+16 -5
View File
@@ -1054,11 +1054,20 @@ fn blocking_replace_mcp_servers_round_trips() {
let codex_home = tmp.path();
let mut servers = BTreeMap::new();
servers.insert("ema".to_string(), serde_json::from_value(serde_json::json!({
"url": "https://ema.example/mcp", "auth": "ema_auth", "scopes": ["tools"],
"oauth_resource": "https://ema.example",
"oauth": {"client_id": "resource-client", "authorization_server_issuer": "https://as.example"}
})).expect("EMA server"));
servers.insert(
"ema".to_string(),
serde_json::from_value(serde_json::json!({
"url": "https://ema.example/mcp", "auth": "ema_auth", "scopes": ["tools"],
"oauth_resource": "https://ema.example",
"oauth": {
"client_id": "resource-client",
"client_secret": "test-ema-secret",
"token_endpoint_auth_method": "client_secret_post",
"authorization_server_issuer": "https://as.example"
}
}))
.expect("EMA server"),
);
servers.insert(
"stdio".to_string(),
McpServerConfig {
@@ -1154,6 +1163,8 @@ oauth_resource = \"https://ema.example\"
[mcp_servers.ema.oauth]
client_id = \"resource-client\"
client_secret = \"test-ema-secret\"
token_endpoint_auth_method = \"client_secret_post\"
authorization_server_issuer = \"https://as.example\"
[mcp_servers.http]