mirror of
https://github.com/openai/codex.git
synced 2026-10-23 18:08:45 +00:00
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:
@@ -226,6 +226,8 @@ fn plugin_host_root_is_retained_in_catalog_identity() {
|
||||
fn ema_policy_survives_rebuilds_and_rebinds_materialized_servers() {
|
||||
let idp = McpServerIdpOAuthConfig {
|
||||
issuer: "https://idp.example".to_string(),
|
||||
client_secret: None,
|
||||
token_endpoint_auth_method: None,
|
||||
client_id: "enterprise-client".to_string(),
|
||||
};
|
||||
let mut builder = ResolvedMcpCatalog::builder();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Account-grant eligibility and registration-specific sign-in requirements.
|
||||
//! The MCP service owns this state across threads; it retains no credentials.
|
||||
//! The MCP service owns this state across threads; it retains registration policy but no user tokens.
|
||||
|
||||
use std::sync::Mutex;
|
||||
|
||||
@@ -66,6 +66,9 @@ impl EmaState {
|
||||
pub(crate) fn account_grant_is_usable(tokens: &TokenData, idp: &McpServerIdpOAuthConfig) -> bool {
|
||||
tokens.oauth_provenance.as_ref().is_some_and(|provenance| {
|
||||
provenance.issuer == idp.issuer
|
||||
// A provisioned secret belongs to the configured client, not another
|
||||
// primary login client at the same IdP. Use dedicated login in that case.
|
||||
&& (idp.client_secret.is_none() || provenance.client_id == idp.client_id)
|
||||
&& tokens
|
||||
.login_owner_id
|
||||
.as_ref()
|
||||
|
||||
@@ -181,7 +181,7 @@ impl EnterpriseTokenSource {
|
||||
let tokens = grant.tokens();
|
||||
// Metadata discovery must not block ordinary account refresh.
|
||||
let identity = resolve_ema_account_idp_identity(
|
||||
&idp.issuer,
|
||||
idp,
|
||||
client_id,
|
||||
&tokens.id_token.raw_jwt,
|
||||
tokens.refresh_token.clone(),
|
||||
@@ -218,6 +218,8 @@ impl EnterpriseTokenSource {
|
||||
resolve_ema_idp_identity(EmaIdpIdentityRequest {
|
||||
issuer: &idp.issuer,
|
||||
client_id,
|
||||
client_secret: idp.client_secret.as_ref().map(|secret| secret.as_str()),
|
||||
token_endpoint_auth_method: idp.token_endpoint_auth_method,
|
||||
credentials: snapshot.acquire_current_ema_credentials().await?,
|
||||
http_client: Arc::clone(&self.http_client),
|
||||
redirect_mode: StreamableHttpRedirectMode::Legacy,
|
||||
@@ -233,6 +235,8 @@ impl EnterpriseTokenSource {
|
||||
resource: self.registration.resource(),
|
||||
scopes: self.registration.scopes(),
|
||||
mcp_client_id: self.registration.client_id(),
|
||||
mcp_client_secret: self.registration.client_secret(),
|
||||
token_endpoint_auth_method: self.registration.token_endpoint_auth_method(),
|
||||
expected_authorization_server_issuer: self
|
||||
.registration
|
||||
.authorization_server_issuer(),
|
||||
|
||||
@@ -186,6 +186,8 @@ fn ema_catalog_allows_only_configured_servers_without_widening_policy() {
|
||||
let mut config = test_mcp_config(PathBuf::new());
|
||||
let idp = codex_config::McpServerIdpOAuthConfig {
|
||||
issuer: "https://idp.example".into(),
|
||||
client_secret: None,
|
||||
token_endpoint_auth_method: None,
|
||||
client_id: "enterprise-client".into(),
|
||||
};
|
||||
let deny_all = codex_protocol::mcp_policy::EnvironmentMcpPolicy {
|
||||
|
||||
Reference in New Issue
Block a user