OAuth 2.0 authentication is supported exclusively for MCP servers using the StreamableHttp transport. Stdio transport servers do not support OAuth through Codex's built-in mechanisms. When configured, Codex performs an OAuth 2.0 authorization code flow with PKCE (Proof Key for Code Exchange) that opens a browser for user authorization, then stores the resulting credentials securely in the system keyring or a fallback file.
The OAuth system consists of four main components:
perform_oauth_login)keyring crate with fallbackOAuth credentials are managed by the codex_rmcp_client crate. The OAuthPersistor wraps an AuthorizationManager from the rmcp SDK and handles automatic token refresh and persistence. The McpConnectionManager integrates these components when establishing StreamableHttp connections.
Sources: codex-rs/rmcp-client/src/oauth.rs1-17 codex-rs/rmcp-client/src/perform_oauth_login.rs1-71 codex-rs/rmcp-client/src/rmcp_client.rs1-64
When a user runs codex mcp login <server-name>, Codex initiates an OAuth 2.0 authorization code flow with PKCE (Proof Key for Code Exchange). The flow involves starting a local HTTP server to receive the authorization callback, opening the user's browser to the authorization URL, and exchanging the authorization code for access and refresh tokens.
OAuth Authorization Flow
Sources: codex-rs/cli/src/mcp_cmd.rs322-363
The OAuth flow is triggered when a StreamableHttp MCP server is configured without a bearer_token_env_var. The McpServerConfig supports the following fields:
| Field | Type | Required | Description |
|---|---|---|---|
url | String | Yes | MCP server endpoint URL |
scopes | Option<Vec<String>> | No | OAuth scopes to request during authorization (can be overridden via --scopes CLI flag) |
bearer_token_env_var | Option<String> | No | Alternative to OAuth: read bearer token from environment variable |
http_headers | Option<HashMap<String, String>> | No | Static HTTP headers to include in requests |
env_http_headers | Option<HashMap<String, String>> | No | HTTP headers read from environment variables |
OAuth vs Environment Variable Authentication:
bearer_token_env_var is set, Codex uses that token and skips OAuth entirelybearer_token_env_var is not set and no OAuth credentials exist, oauth_login_support checks if the server supports OAuthcodex mcp loginExample Configuration:
Sources: codex-rs/core/src/config/types.rs44-80 codex-rs/cli/src/mcp_cmd.rs265-285 codex-rs/cli/src/mcp_cmd.rs331-350
The OauthLoginFlow starts a temporary tiny_http::Server on localhost to receive the OAuth callback. The server lifecycle is managed by a CallbackServerGuard that ensures cleanup:
Callback Configuration:
Callback Flow:
resolve_callback_port determines the bind port (configured or random)callback_bind_host determines whether to bind to 127.0.0.1 or 0.0.0.0 based on callback URLServer::http(bind_addr) starts the serverspawn_callback_server spawns a blocking task that waits for HTTP requestsparse_oauth_callback validates the callback path and extracts code and state query parametersCallbackServerGuard::drop calls server.unblock() to shut down the serverThe callback server runs only during the OAuth flow and is automatically cleaned up when the OauthLoginFlow is dropped.
Sources: codex-rs/rmcp-client/src/perform_oauth_login.rs108-146 codex-rs/rmcp-client/src/perform_oauth_login.rs238-296 codex-rs/rmcp-client/src/perform_oauth_login.rs32-40
Codex supports three credential storage modes via the OAuthCredentialsStoreMode enum:
| Mode | Description | Behavior |
|---|---|---|
Auto (default) | Try keyring, fallback to file | Attempts keyring crate storage; falls back to ~/.codex/.credentials.json on failure |
File | Always use file storage | Directly writes to ~/.codex/.credentials.json |
Keyring | Require keyring storage | Fails if keyring is unavailable (no fallback) |
Platform-Specific Keyring Backends:
The keyring crate uses platform-specific secure storage:
apple-native feature → macOS Keychainwindows-native feature → Windows Credential Managerlinux-native-async-persistent feature → kernel keyutils + async-secret-service (DBus Secret Service)sync-secret-service feature → DBus Secret ServiceThe keyring service name is "Codex MCP Credentials" and the account key is compute_store_key(server_name, url).
Credential Storage Architecture
Sources: codex-rs/rmcp-client/src/oauth.rs66-79 codex-rs/rmcp-client/src/oauth.rs94-110 codex-rs/rmcp-client/Cargo.toml63-73 codex-rs/rmcp-client/src/oauth.rs53-54
OAuth credentials are stored using a composite key derived from (server_name, server_url). The compute_store_key function creates a SHA-256 hash to ensure key uniqueness and avoid special characters:
StoredOAuthTokens Structure:
Example Keyring Entry:
Service: "Codex MCP Credentials"
Account: "a1b2c3d4e5f6..." (SHA-256 hash)
Secret: {"server_name":"github","url":"https://api.github.com/mcp","client_id":"...","token_response":{...},"expires_at":1234567890000}
Example File Entry (~/.codex/.credentials.json):
Sources: codex-rs/rmcp-client/src/oauth.rs56-64 codex-rs/rmcp-client/src/oauth.rs81-92 codex-rs/rmcp-client/src/oauth.rs248-294
The OAuthPersistor class manages the OAuth token lifecycle, including automatic refresh and persistence. It wraps an AuthorizationManager from the rmcp crate's auth module:
The OAuthPersistor is created during MCP client initialization and stored in ClientState::Ready within RmcpClient.
Sources: codex-rs/rmcp-client/src/oauth.rs295-336
Token refresh is triggered automatically by the AuthClient wrapper around reqwest::Client. The refresh logic uses the following parameters:
30_000 (30 seconds) - tokens are refreshed 30 seconds before expirationexpires_in_from_timestamp(expires_at) calculates remaining validity from the stored expires_at fieldcompute_expires_at_millis(token_response) computes the absolute expiration timestamp from expires_inToken Refresh Flow with OAuthPersistor:
Sources: codex-rs/rmcp-client/src/oauth.rs338-431 codex-rs/rmcp-client/src/rmcp_client.rs569-583 codex-rs/rmcp-client/src/oauth.rs54
The compute_expires_at_millis function converts relative expires_in to absolute Unix timestamp:
When loading tokens from storage, refresh_expires_in_from_timestamp recalculates expires_in from the stored expires_at to maintain compatibility with the rmcp SDK's token expiration checks.
Sources: codex-rs/rmcp-client/src/oauth.rs433-445 codex-rs/rmcp-client/src/oauth.rs120-134
Codex tracks the authentication status of each configured MCP server and displays this information in codex mcp list. The McpAuthStatus enum defines four possible states:
| Status | Description | Displayed When |
|---|---|---|
Authenticated | Valid OAuth credentials are stored | Credentials exist and are not expired |
Unauthenticated | OAuth is supported but user has not logged in | StreamableHttp server with no stored credentials |
AuthenticationError | OAuth flow failed or credentials are invalid | Token refresh failed or authorization was denied |
Unsupported | Server does not support OAuth | Stdio transport or bearer_token_env_var is configured |
Sources: codex-rs/cli/src/mcp_cmd.rs395-409 codex-rs/cli/src/mcp_cmd.rs499-503
The compute_auth_statuses function in codex_core::mcp::auth checks credential storage for each configured server and returns a map of authentication states. This computation happens when displaying server lists or checking server readiness.
Auth Status Detection Flow
Sources: codex-rs/cli/src/mcp_cmd.rs405-409 codex-rs/cli/src/mcp_cmd.rs16-18
The codex mcp list command computes and displays the auth status for each server via compute_auth_statuses:
The auth status column indicates:
Authenticated: OAuth credentials exist in storageUnauthenticated: OAuth is supported but no credentials storedUnsupported: Server uses bearer_token_env_var or Stdio transportSources: codex-rs/cli/src/mcp_cmd.rs407-411 codex-rs/cli/src/mcp_cmd.rs519-535
codex mcp loginInitiates the OAuth flow for a configured StreamableHttp MCP server. The command opens a browser window for user authorization and stores the resulting credentials.
Usage:
Behavior:
~/.codex/config.tomlScope Override:
If --scopes is provided, it overrides the scopes field in the config file for this login session.
Sources: codex-rs/cli/src/mcp_cmd.rs135-143 codex-rs/cli/src/mcp_cmd.rs322-363
codex mcp logoutRemoves stored OAuth credentials for an MCP server.
Usage:
Behavior:
After logout, the server's auth status becomes Unauthenticated, and the user must run codex mcp login again to restore access.
Sources: codex-rs/cli/src/mcp_cmd.rs145-149 codex-rs/cli/src/mcp_cmd.rs365-393
When adding a new StreamableHttp server via codex mcp add, Codex automatically detects if the server supports OAuth and prompts the user to log in:
This detection is performed by the oauth_login_support function, which checks the transport type and determines if OAuth is likely required.
Sources: codex-rs/cli/src/mcp_cmd.rs265-284
When the McpConnectionManager initializes a StreamableHttp server, it passes OAuthCredentialsStoreMode to make_rmcp_client, which creates an RmcpClient via RmcpClient::new_streamable_http_client. The client loads stored credentials using load_oauth_tokens and creates an OAuth-enabled transport:
Credential Resolution Flow
Sources: codex-rs/core/src/mcp_connection_manager.rs974-1016 codex-rs/rmcp-client/src/rmcp_client.rs238-318 codex-rs/rmcp-client/src/rmcp_client.rs586-632
If bearer_token_env_var is configured, Codex reads the token from the environment and skips OAuth. The load_oauth_tokens function returns None when a bearer token env var is set, and RmcpClient::new_streamable_http_client creates a simple authenticated transport:
Bearer Token Flow:
The token is included in the Authorization: Bearer <token> header via StreamableHttpClientTransportConfig::auth_header().
Sources: codex-rs/rmcp-client/src/rmcp_client.rs248-259 codex-rs/rmcp-client/src/rmcp_client.rs302-311
Keyring Storage (Preferred):
File Storage (Fallback):
~/.codex/.credentials.jsonOAuthCredentialsStoreMode::File, or keyring write failureBest Practices:
| ✓ DO | ✗ DON'T |
|---|---|
Use Auto or Keyring mode when possible | Use File mode in production environments |
Verify file permissions on .credentials.json | Commit .credentials.json to version control |
| Rotate OAuth tokens via re-login | Share keyring access across untrusted apps |
| Monitor credential storage location changes | Store production tokens in dev keychain |
Sources: codex-rs/rmcp-client/src/oauth.rs1-17 codex-rs/rmcp-client/src/oauth.rs66-79
When using bearer_token_env_var instead of OAuth:
Best Practices:
pass, 1Password CLI, vault) to populate environment variablesSources: codex-rs/rmcp-client/src/rmcp_client.rs248-259
OAuth-authenticated MCP servers support the same tool filtering mechanisms as unauthenticated servers. The enabled_tools and disabled_tools fields can be used to restrict which tools from an authenticated server are available to the Codex agent.
Sources: codex-rs/core/src/config/types.rs69-75
OAuth authentication flows may require additional time for token validation. The startup_timeout_sec field can be increased to accommodate slower authentication handshakes:
Refresh this wiki