You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
OAuth authentication fails at issuer validation #31573
Reproduced on 0.143.0 against a self-hosted MCP server (custom OAuth 2.1 AS, RFC 9207 compliant). I traced this end to end — it's a bug in Codex's callback handler, not in rmcp. rmcp already exposes the correct API; Codex calls the wrong variant and drops the iss before validation.
TL;DR
Our AS puts a valid iss in the authorization response (RFC 9207). Codex parses the loopback callback, discards iss, then calls the issuer-less handle_callback(code, state). rmcp meanwhile sets require_issuer = true from the AS metadata flag authorization_response_iss_parameter_supported, so it demands an iss it was never handed → AuthorizationServerMissingIssuer. It fails 100% of the time for any spec-compliant AS, even though a correct iss is on the wire.
Evidence — the iss really is in the callback
Captured the exact URL the browser delivers to Codex's loopback server:
OauthCallbackResult { code, state } has no field to carry it.
OauthLoginFlow::finish() then calls the issuer-less variant:
self.oauth_state.handle_callback(&code,&csrf_state)// issuer = None.await.context("failed to handle OAuth callback")?;// <-- the error wrapper users see
Why rmcp then rejects it — crates/rmcp/src/transport/auth.rs
(paths/lines on main at time of writing)
get_authorization_url() derives require_issuer straight from the AS metadata flag (≈ L1373):
let require_issuer = metadata.additional_fields.get("authorization_response_iss_parameter_supported").and_then(|v| v.as_bool()).unwrap_or(false);
Option B: keep the raw callback URL and hand it to self.oauth_state.handle_callback_url(&full_url), letting rmcp parse code / state / iss itself.
This lines up with @NiceWaffel's guess about modelcontextprotocol/rust-sdk#896: that PR added issuer enforcement to rmcp. Codex's callback handler never forwarded iss, so the new enforcement surfaced the latent gap — hence "works in 0.141.0, broken in 0.143.0."
Reacted by Jacob Evans, AIKAWA Shigechika, Paul Yakubets, Dmytro Shamsiiev and 5 more
Confirmed on 0.143.0 and 0.144.0 — the issis present in the callback; Codex just doesn't forward it to the validator.
The check itself (in bundled rmcp from modelcontextprotocol/rust-sdk#896, crates/rmcp/src/transport/auth.rs) is correct: AuthorizationServerMissingIssuer fires only when require_issuer == true (derived from the AS advertising authorization_response_iss_parameter_supported: true) andreceived_issuer == None. And AuthorizationCallback::from_redirect_url parses iss from the query string just fine.
So the bug is in Codex's caller wiring: it reaches the validator with received_issuer = None even when the authorization server redirects back with a valid iss. Likely Codex is calling exchange_code_for_token (which passes None) instead of exchange_code_for_token_with_issuer, or isn't propagating the parsed AuthorizationCallback.issuer.
Minimal repro (no real IdP needed). A stub authorization server that advertises the flag and sends iss on the callback:
Dropping authorization_response_iss_parameter_supported from the AS metadata makes login succeed — confirming the callback's iss is never consumed and the check is gated purely on the advertised flag.
This breaks OAuth login against fully RFC 9207-compliant servers (e.g. any node-oidc-provider-based AS, which sends iss and advertises support by default). Downgrading isn't a workable fix since newer models require newer Codex.
Reacted by AIKAWA Shigechika, Paul Yakubets, Dmytro Shamsiiev and 4 more
Independent reproduction on Codex CLI 0.144.1 (aarch64-apple-darwin, macOS 26.5.1) against a local Auto-K MCP server confirms the callback adapter drops a valid RFC 9207 issuer.
The actual browser request to Codex's loopback listener contained the expected, percent-encoded iss (authorization code, state, and callback identifier redacted):
Error: failed to handle OAuth callback
Caused by:
Authorization server response missing required issuer: expected https://localhost:3000/api/auth
I reproduced the source-level diagnosis on the rust-v0.144.1 tag. In codex-rs/rmcp-client/src/perform_oauth_login.rs, OauthCallbackResult carries only code and state; parse_oauth_callback ignores iss; and finish calls the issuer-less handle_callback(&code, &csrf_state).
I then applied the minimal fix locally:
Add issuer: Option<String> to OauthCallbackResult.
Parse the iss query parameter (including percent decoding).
Add a regression test using iss=https%3A%2F%2Fauth.example.com.
Fail-before evidence:
error[E0560]: struct `OauthCallbackResult` has no field named `issuer`
Pass-after evidence:
running 7 tests
...
test perform_oauth_login::tests::parse_oauth_callback_preserves_authorization_server_issuer ... ok
test result: ok. 7 passed; 0 failed
After building the patched 0.144.1 CLI, the unchanged authorization server and MCP stack completed the real flow:
Successfully logged in to MCP server 'autok_local'.
This isolates the failure to Codex's callback plumbing. The authorization server emits the correct iss, and rmcp correctly enforces it after the server advertises RFC 9207 support.
Reacted by AIKAWA Shigechika, Paul Yakubets, Dmytro Shamsiiev and 5 more
The authorization server is Better Auth 1.6.23 with issuer http://localhost:3000/api/auth. Dynamic registration, authorization, login, and consent complete successfully, but Codex reports:
Authorization server response missing required issuer:
expected http://localhost:3000/api/auth
This matches the callback-plumbing regression described above. A temporary compatibility workaround is to omit authorization_response_iss_parameter_supported from the MCP-discovered authorization-server metadata.
Confirmed root cause — this is a callback-parsing regression in Codex, not the auth server.
Our MCP server sits in front of a standard OAuth 2.1 / OIDC authorization server that does emit the RFC 9207 iss parameter and advertises authorization_response_iss_parameter_supported: true. Captured the literal redirect the AS sends to the loopback callback (values redacted):
GET /callback?state=<state>
&iss=https%3A%2F%2Fauth.example.com
&code=<code>
So iss is present and correct on the wire. Codex still fails with:
Authorization server response missing required issuer:
expected https://auth.example.com
The reason: in codex-rs/rmcp-client/src/perform_oauth_login.rs, the callback query parser only matches code, state, error, error_description — iss hits the _ => {} arm and is dropped. It then calls the no-issuer variant oauth_state.handle_callback(&code, &csrf_state), which passes received_issuer = None into rmcp.
Since rmcp modelcontextprotocol/rust-sdk#896 sets require_issuer from authorization_response_iss_parameter_supported, and received_issuer is None, validate_authorization_response_issuer returns AuthorizationServerMissingIssuer — even though the AS did send iss. That's why 0.141 works (pre-that-change, no enforcement) and 0.143+ breaks.
Fix: have the callback parser capture iss and pass it through — either use the SDK's AuthorizationCallback::from_redirect_url / handle_callback_url, or add "iss" => iss = Some(decoded) and call handle_callback_with_issuer(&code, &csrf_state, iss.as_deref()).
Reproduced: fails on 0.144.1, succeeds on 0.141.0 against the identical endpoint.
Configure the server as a Streamable HTTP MCP server.
Run:
codex mcp login n8n_test
Complete n8n's consent page. Dynamic client registration and consent succeed, and n8n lists Codex under connected OAuth clients.
n8n redirects to Codex's loopback callback, which displays:
Authentication complete. You may close this window.
The CLI then fails with:
Error: failed to handle OAuth callback
Caused by:
Authorization server response missing required issuer: expected https://<redacted-n8n-host>
codex mcp list still reports the server as Not logged in.
This is reproducible through both Codex Desktop authentication and the bundled codex mcp login command. Changing browsers does not affect the result.
Expected behavior
Codex should preserve the RFC 9207 iss authorization-response parameter, validate it against the issuer advertised by the authorization server, exchange the authorization code, and store the OAuth credentials.
This appears to be the same callback-plumbing regression already diagnosed in this issue: parse_oauth_callback() drops iss, after which Codex calls the issuer-less handle_callback(&code, &csrf_state) variant. The n8n 2.30.1 implementation explicitly includes iss in successful and error authorization redirects:
Been having the same issue as well for almost a month now using Keycloak to authorize. We ended up having to roll back to 0.141 to authorize before rolling forward to latest because token exchange still works after the initial authorization, but we're relying heavily on nobody's refresh token expiring with this setup.
Confirming this is still present in 0.146.0, the current latest stable (codex mcp add agentcat --url https://mcp.agentcat.com/mcp, macOS arm64).
Seems pretty important to get this in soon as we know many MCP servers adopting the new OAuth spec. That said, it's frustrating how much of a moving window auth is so your team only has my complete understanding on this matter 😆
OAuth login failed for me still in codex-cli 0.146.1; but doing it through the 'unified ChatGPT' (aka Codex) desktop app Version 26.730.61639 (Released Aug 5, 2026) on macOS Tahoe 26.6 (25G72) seemed to work.
The browser authorization flow completed successfully. From a fresh Codex session, I then verified authenticated calendar and mail access with successful list_calendars, search_events, and search_email calls. No older-CLI workaround was needed.
I confirm it is fixed in 0.147.0, tested with Fakturoid MCP:
codex --version
codex-cli 0.146.1
codex mcp add fakturoid --url https://app.fakturoid.cz/mcp/v1/
Added global MCP server 'fakturoid'.
Detected OAuth support. Starting OAuth flow…
Authorize `fakturoid` by opening this URL in your browser:
https://app.fakturoid.cz/mcp/v1/oauth....
Error: failed to handle OAuth callback
Caused by:
Authorization server response missing required issuer: expected https://app.fakturoid.cz/mcp/v1
codex update
codex --version
codex-cli 0.147.0
codex mcp add fakturoid --url https://app.fakturoid.cz/mcp/v1/
Added global MCP server 'fakturoid'.
Detected OAuth support. Starting OAuth flow…
Authorize `fakturoid` by opening this URL in your browser:
https://app.fakturoid.cz/mcp/v1/oauth....
Successfully logged in.
Quick heads-up: this OAuth issuer-validation regression is fixed in stable CLI 0.147.0.
Method:
Upgrade Codex CLI to 0.147.0 or newer (codex update, or reinstall).
Retry codex mcp login <server> against the same authorization server.
Evidence: multiple users in this thread confirmed the fix in stable 0.147.0 with Fastmail's and Fakturoid's official OAuth MCP servers (the fix first shipped in 0.147.0-alpha.1); the regression affected the 0.143.x-0.146.x range, where the RFC 9207 iss value was dropped before validation, while 0.141.0 remained compliant.
Independent community verification; not an official OpenAI statement.
Activity
fatalist commentedon Jul 9, 2026
Reproduced on
0.143.0against a self-hosted MCP server (custom OAuth 2.1 AS, RFC 9207 compliant). I traced this end to end — it's a bug in Codex's callback handler, not inrmcp.rmcpalready exposes the correct API; Codex calls the wrong variant and drops theissbefore validation.TL;DR
Our AS puts a valid
issin the authorization response (RFC 9207). Codex parses the loopback callback, discardsiss, then calls the issuer-lesshandle_callback(code, state).rmcpmeanwhile setsrequire_issuer = truefrom the AS metadata flagauthorization_response_iss_parameter_supported, so it demands anissit was never handed →AuthorizationServerMissingIssuer. It fails 100% of the time for any spec-compliant AS, even though a correctissis on the wire.Evidence — the
issreally is in the callbackCaptured the exact URL the browser delivers to Codex's loopback server:
iss=https://api.example.comis present and equal to the discoveryissuer. Codex throws it away before validation.Root cause —
codex-rs/rmcp-client/src/perform_oauth_login.rs(commit
2c85975)parse_oauth_callback()only capturescode/state/error/error_description; theissparam falls into the catch-all and is dropped:OauthCallbackResult { code, state }has no field to carry it.OauthLoginFlow::finish()then calls the issuer-less variant:Why
rmcpthen rejects it —crates/rmcp/src/transport/auth.rs(paths/lines on
mainat time of writing)get_authorization_url()derivesrequire_issuerstraight from the AS metadata flag (≈ L1373):OAuthState::handle_callback→AuthorizationSession::handle_callback→handle_callback_with_issuer(code, csrf, None)→exchange_code_for_token_with_issuer(code, csrf, None).validate_authorization_response_issuer()(≈ L1528) withreceived_issuer = Noneandrequire_issuer = truereturnsAuthError::AuthorizationServerMissingIssuer(≈ L1544-1548).So the RFC 9207 check
rmcpadded is correct; the value simply never reaches it because Codex didn't forward it.The fix —
rmcpalready provides the right entry pointsOAuthStateexposes all three (≈ L3146-3179):handle_callback(code, csrf)— issuer-less (current call)handle_callback_with_issuer(code, csrf, Option<&str>)handle_callback_url(callback_url)— parses the full redirect URL viaAuthorizationCallback::from_redirect_url, which does extractiss(≈ L2697-2707)Option A (minimal): capture
issinparse_oauth_callbackand thread it through:Option B: keep the raw callback URL and hand it to
self.oauth_state.handle_callback_url(&full_url), lettingrmcpparsecode/state/issitself.This lines up with @NiceWaffel's guess about modelcontextprotocol/rust-sdk#896: that PR added issuer enforcement to
rmcp. Codex's callback handler never forwardediss, so the new enforcement surfaced the latent gap — hence "works in 0.141.0, broken in 0.143.0."tobernguyen commentedon Jul 9, 2026
Confirmed on 0.143.0 and 0.144.0 — the
issis present in the callback; Codex just doesn't forward it to the validator.The check itself (in bundled
rmcpfrom modelcontextprotocol/rust-sdk#896,crates/rmcp/src/transport/auth.rs) is correct:AuthorizationServerMissingIssuerfires only whenrequire_issuer == true(derived from the AS advertisingauthorization_response_iss_parameter_supported: true) andreceived_issuer == None. AndAuthorizationCallback::from_redirect_urlparsesissfrom the query string just fine.So the bug is in Codex's caller wiring: it reaches the validator with
received_issuer = Noneeven when the authorization server redirects back with a validiss. Likely Codex is callingexchange_code_for_token(which passesNone) instead ofexchange_code_for_token_with_issuer, or isn't propagating the parsedAuthorizationCallback.issuer.Minimal repro (no real IdP needed). A stub authorization server that advertises the flag and sends
isson the callback:{ "issuer": "http://127.0.0.1:9700", "authorization_endpoint": ".../authorize", "token_endpoint": ".../token", "authorization_response_iss_parameter_supported": true }/authorize→302to the loopbackredirect_uriwith?code=...&state=<echoed>&iss=http%3A%2F%2F127.0.0.1%3A9700The redirect Codex receives demonstrably contains
iss:Dropping
authorization_response_iss_parameter_supportedfrom the AS metadata makes login succeed — confirming the callback'sissis never consumed and the check is gated purely on the advertised flag.This breaks OAuth login against fully RFC 9207-compliant servers (e.g. any
node-oidc-provider-based AS, which sendsissand advertises support by default). Downgrading isn't a workable fix since newer models require newer Codex.CaliLuke commentedon Jul 10, 2026
Independent reproduction on Codex CLI 0.144.1 (
aarch64-apple-darwin, macOS 26.5.1) against a local Auto-K MCP server confirms the callback adapter drops a valid RFC 9207 issuer.Authorization-server metadata returned:
{ "issuer": "https://localhost:3000/api/auth", "authorization_response_iss_parameter_supported": true }The actual browser request to Codex's loopback listener contained the expected, percent-encoded
iss(authorization code, state, and callback identifier redacted):Codex nevertheless failed with:
I reproduced the source-level diagnosis on the
rust-v0.144.1tag. Incodex-rs/rmcp-client/src/perform_oauth_login.rs,OauthCallbackResultcarries onlycodeandstate;parse_oauth_callbackignoresiss; andfinishcalls the issuer-lesshandle_callback(&code, &csrf_state).I then applied the minimal fix locally:
issuer: Option<String>toOauthCallbackResult.issquery parameter (including percent decoding).handle_callback_with_issuer(&code, &csrf_state, issuer.as_deref()).iss=https%3A%2F%2Fauth.example.com.Fail-before evidence:
Pass-after evidence:
After building the patched 0.144.1 CLI, the unchanged authorization server and MCP stack completed the real flow:
This isolates the failure to Codex's callback plumbing. The authorization server emits the correct
iss, andrmcpcorrectly enforces it after the server advertises RFC 9207 support.issis present in callback #32472 as a duplicate of this issueevannotfound commentedon Jul 12, 2026
Reproduced with
codex-cli 0.144.0-alpha.4, using the binary bundled with ChatGPT.app on macOS:The authorization server is Better Auth 1.6.23 with issuer
http://localhost:3000/api/auth. Dynamic registration, authorization, login, and consent complete successfully, but Codex reports:This matches the callback-plumbing regression described above. A temporary compatibility workaround is to omit
authorization_response_iss_parameter_supportedfrom the MCP-discovered authorization-server metadata.microHoffman commentedon Jul 13, 2026
Same issue happening on 0.144.3 on Linux.
aditya-findem commentedon Jul 14, 2026
Same issue on 0.144.3.
Downgrading to 0.141.0 works.
antoniocasagrande-airia commentedon Jul 14, 2026
Confirmed root cause — this is a callback-parsing regression in Codex, not the auth server.
Our MCP server sits in front of a standard OAuth 2.1 / OIDC authorization server that does emit the RFC 9207
issparameter and advertisesauthorization_response_iss_parameter_supported: true. Captured the literal redirect the AS sends to the loopback callback (values redacted):So
issis present and correct on the wire. Codex still fails with:The reason: in
codex-rs/rmcp-client/src/perform_oauth_login.rs, the callback query parser only matchescode,state,error,error_description—isshits the_ => {}arm and is dropped. It then calls the no-issuer variantoauth_state.handle_callback(&code, &csrf_state), which passesreceived_issuer = Noneinto rmcp.Since rmcp modelcontextprotocol/rust-sdk#896 sets
require_issuerfromauthorization_response_iss_parameter_supported, andreceived_issuerisNone,validate_authorization_response_issuerreturnsAuthorizationServerMissingIssuer— even though the AS did sendiss. That's why 0.141 works (pre-that-change, no enforcement) and 0.143+ breaks.Fix: have the callback parser capture
issand pass it through — either use the SDK'sAuthorizationCallback::from_redirect_url/handle_callback_url, or add"iss" => iss = Some(decoded)and callhandle_callback_with_issuer(&code, &csrf_state, iss.as_deref()).Reproduced: fails on 0.144.1, succeeds on 0.141.0 against the identical endpoint.
batmanwarrior commentedon Jul 14, 2026
Same issue on 0.144.4
aditya-findem commentedon Jul 14, 2026
Workaround for anyone/any agents reading this:
Temporary workaround for npm-installed Codex CLI users:
This avoids the OAuth callback regression in Codex 0.143.x–0.144.3. Upgrade again once the issue is fixed.
Lyhtande commentedon Jul 15, 2026
Independent reproduction with n8n's official instance-level MCP server.
Environment
26.707.722210.144.2https://<redacted-n8n-host>/mcp-server/http2.30.1, behind a reverse proxyAuthorization-server metadata
GET https://<redacted-n8n-host>/.well-known/oauth-authorization-serverreturns the following structure (hostname replaced):{ "issuer": "https://<redacted-n8n-host>", "authorization_endpoint": "https://<redacted-n8n-host>/mcp-oauth/authorize", "token_endpoint": "https://<redacted-n8n-host>/mcp-oauth/token", "registration_endpoint": "https://<redacted-n8n-host>/mcp-oauth/register", "revocation_endpoint": "https://<redacted-n8n-host>/mcp-oauth/revoke", "response_types_supported": ["code"], "grant_types_supported": ["authorization_code", "refresh_token"], "token_endpoint_auth_methods_supported": ["none", "client_secret_post", "client_secret_basic"], "code_challenge_methods_supported": ["S256"], "authorization_response_iss_parameter_supported": true }The protected-resource metadata correctly points to the same authorization server:
{ "resource": "https://<redacted-n8n-host>/mcp-server/http", "bearer_methods_supported": ["header"], "authorization_servers": ["https://<redacted-n8n-host>"] }Reproduction
Configure the server as a Streamable HTTP MCP server.
Run:
Complete n8n's consent page. Dynamic client registration and consent succeed, and n8n lists
Codexunder connected OAuth clients.n8n redirects to Codex's loopback callback, which displays:
The CLI then fails with:
codex mcp liststill reports the server asNot logged in.This is reproducible through both Codex Desktop authentication and the bundled
codex mcp logincommand. Changing browsers does not affect the result.Expected behavior
Codex should preserve the RFC 9207
issauthorization-response parameter, validate it against the issuer advertised by the authorization server, exchange the authorization code, and store the OAuth credentials.This appears to be the same callback-plumbing regression already diagnosed in this issue:
parse_oauth_callback()dropsiss, after which Codex calls the issuer-lesshandle_callback(&code, &csrf_state)variant. The n8n 2.30.1 implementation explicitly includesissin successful and error authorization redirects:https://github.com/n8n-io/n8n/blob/n8n%402.30.1/packages/cli/src/modules/oauth-server/oauth.helpers.ts
No authorization codes, access tokens, refresh tokens, client secrets, or session identifiers are included in this report.
16 remaining items
warbadata commentedon Jul 28, 2026
Been having the same issue as well for almost a month now using Keycloak to authorize. We ended up having to roll back to 0.141 to authorize before rolling forward to latest because token exchange still works after the initial authorization, but we're relying heavily on nobody's refresh token expiring with this setup.
emlazzarin commentedon Jul 28, 2026
Anybody have tips on getting this merged, or know who to contact?
emlazzarin commentedon Jul 29, 2026
The fix is in 0.147.0-alpha.1
warbadata commentedon Jul 30, 2026
Verified the fix works. There's still a regression with claim handling vs 142 but that's a separate issue and not blocking. Thanks!
kashishhora commentedon Jul 30, 2026
Confirming this is still present in 0.146.0, the current latest stable (
codex mcp add agentcat --url https://mcp.agentcat.com/mcp, macOS arm64).Seems pretty important to get this in soon as we know many MCP servers adopting the new OAuth spec. That said, it's frustrating how much of a moving window auth is so your team only has my complete understanding on this matter 😆
docs: record the 2026-07-30 live client results
domi-zabe-scacap commentedon Jul 31, 2026
When is this coming to the App?
feat: require S256 PKCE by default (#277)
0xdevalias commentedon Aug 7, 2026
OAuth login failed for me still in
codex-cli 0.146.1; but doing it through the 'unified ChatGPT' (aka Codex) desktop appVersion 26.730.61639 (Released Aug 5, 2026)onmacOS Tahoe 26.6 (25G72)seemed to work.jgennari commentedon Aug 7, 2026
Confirmed fixed for me in stable Codex CLI
0.147.0using Fastmail’s official OAuth MCP server.After upgrading from
0.145.0, I ran:codex mcp login fastmail \ --scopes 'https://www.fastmail.com/dev/mcp,offline_access'The browser authorization flow completed successfully. From a fresh Codex session, I then verified authenticated calendar and mail access with successful
list_calendars,search_events, andsearch_emailcalls. No older-CLI workaround was needed.This appears resolved by #35720.
iliferov commentedon Aug 8, 2026
I confirm it is fixed in
0.147.0, tested with Fakturoid MCP:fix(checks): let the comment allowlist hold more than one note per file
fix(codex): bearer fallback for Cloudflare MCP while codex OAuth is b…
naipi11 commentedon Aug 18, 2026
Quick heads-up: this OAuth issuer-validation regression is fixed in stable CLI 0.147.0.
Method:
0.147.0or newer (codex update, or reinstall).codex mcp login <server>against the same authorization server.Evidence: multiple users in this thread confirmed the fix in stable
0.147.0with Fastmail's and Fakturoid's official OAuth MCP servers (the fix first shipped in0.147.0-alpha.1); the regression affected the 0.143.x-0.146.x range, where the RFC 9207issvalue was dropped before validation, while0.141.0remained compliant.Independent community verification; not an official OpenAI statement.
Gateway: optional GATEWAY_ADVERTISE_ISS_PARAM (default true) as worka…