OAuth authentication fails at issuer validation
Open 💬 36 comments Opened Jul 8, 2026 by NiceWaffel
What version of Codex CLI is running?
0.143.0
What subscription do you have?
Free
Which model were you using?
_No response_
What platform is your computer?
_No response_
What terminal emulator and version are you using (if applicable)?
_No response_
Codex doctor report
What issue are you seeing?
Trying to log in to an MCP Server using the Authorization Code flow fails with an error:
Error: failed to handle OAuth callback
Caused by:
Authorization server response missing required issuer: expected https://my.mcp.server/issuer
This issue was introduced recently (works in 0.141.0 for example). I assume it is related to https://github.com/modelcontextprotocol/rust-sdk/pull/896
What steps can reproduce the bug?
- Setup an MCP server with OAuth login
- Start logging in using
codex mcp login my_mcp_server
What is the expected behavior?
Codex should handle the issuer contained in the iss parameter of the callback URL (see RFC 9207).
Additional information
_No response_
36 Comments
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:``
rustissmatch key {
"code" => code = Some(decoded),
"state" => state = Some(decoded),
"error" => error = Some(decoded),
"error_description" => error_description = Some(decoded),
_ => {} // <--
discarded here
`}
OauthCallbackResult { code, state }` has no field to carry it.OauthLoginFlow::finish()then calls the issuer-less variant:``
rust
``self.oauth_state
.handle_callback(&code, &csrf_state) // issuer = None
.await
.context("failed to handle OAuth callback")?; // <-- the error wrapper users see
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):``
rust
``let require_issuer = metadata.additional_fields
.get("authorization_response_iss_parameter_supported")
.and_then(|v| v.as_bool())
.unwrap_or(false);
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."**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.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:
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.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.Same issue happening on 0.144.3 on Linux.
Same issue on 0.144.3.
Downgrading to 0.141.0 works.
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.
Same issue on 0.144.4
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.
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):The protected-resource metadata correctly points to the same authorization server:
Reproduction
``
text
``codex mcp login n8n_test
Codexunder connected OAuth clients.``
text
``Authentication complete. You may close this window.
```text
Error: failed to handle OAuth callback
Caused by:
Authorization server response missing required issuer: expected https://<redacted-n8n-host>
```
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.
Independent reproduction on Codex CLI
0.144.4using an RFC 9207-compatible authorization server.The flow is a standard MCP OAuth authorization-code flow with PKCE:
issuerandauthorization_response_iss_parameter_supported: true.code,state, and a percent-encodedissexactly matching the discovered issuer.Authentication complete. You may close this window.We verified that the callback arriving at Codex has this shape:
The
issvalue is therefore present and correct on the wire. This matches the source-level diagnosis already discussed here:parse_oauth_callback()retainscodeandstatebut ignoresiss, after which Codex calls the issuer-lesshandle_callback(&code, &csrf_state)API.As a confirmation, temporarily advertising
authorization_response_iss_parameter_supported: falsewhile leaving the actualissresponse parameter unchanged makes the same Codex login complete successfully. That isolates the failure to the callback issuer not being forwarded into validation.It would be helpful for Codex to preserve
issand callhandle_callback_with_issuer(...)(or pass the full callback URL to the SDK), with a regression test covering an authorization server that both advertises RFC 9207 support and returns a matching issuer.I reproduced this regression and prepared a focused fix: https://github.com/kriptoburak/codex/commit/acb1f572d689a2f15378c8e831d7d6eb3be292c2
The callback adapter currently discards the decoded RFC 9207 iss value and then invokes the issuer-less rmcp callback handler. The patch preserves iss in OauthCallbackResult and passes it to handle_callback_with_issuer, with regression coverage for a percent-encoded issuer.
Verification:
I could not open a PR because this repository currently limits pull requests to collaborators. A maintainer can cherry-pick the commit or enable an external PR for the branch: https://github.com/kriptoburak/codex/tree/codex/preserve-mcp-oauth-issuer
please contribute to the discussion https://github.com/openai/codex/discussions/33682 to raise visibility with the team. The MCP bug reports are very clearly being ignored right now.
rust-v0.144.5(latest stable) andrust-v0.145.0-alpha.20both still call the issuer-lesshandle_callbackatcodex-rs/rmcp-client/src/perform_oauth_login.rs:606, so the fix described above doesn't seem to have shipped in a release yet.This blocks every interactive OAuth MCP login from Codex against any RFC 9207-compliant authorization server.
Would be great to get the one-line fix (
handle_callback_with_issuer/handle_callback_url) into an upcoming 0.145 build?Reproduced on
codex-cli 0.145.0-alpha.18bundled with the Codex macOS app against Keycloak26.3.1.The authorization server advertises
authorization_response_iss_parameter_supported: true, the Keycloak client hasexclude.issuer.from.auth.response=false(confirmed both in the Admin UI and directly in Keycloak's PostgreSQLclient_attributestable), and the browser authorization flow still fails with:Source inspection of the exact
rust-v0.145.0-alpha.18tag confirmsparse_oauth_callback()discardsissandfinish()still calls the issuer-lesshandle_callback(&code, &csrf_state), while bundledrmcp 1.8.0exposeshandle_callback_with_issuer(...).I also traced the regression boundary across official tags:
rust-v0.143.0-alpha.6:rmcp 1.7.0, unaffectedrust-v0.143.0-alpha.7:rmcp 1.8.0, first affected alpharust-v0.142.5: last unaffected stable releaserust-v0.143.0: first affected stable releaseRunning
npx -y @openai/codex@0.142.5 mcp login <server>completes successfully against the same unchanged Keycloak and MCP deployment, confirming this is the Codex callback integration regression rather than an authorization-server configuration issue.Reproduced with Google Workspace remote MCP on macOS arm64 using bundled
codex-cli 0.145.0-alpha.18.Server
https://drivemcp.googleapis.com/mcp/v1Reproduction
Codex generates an authorization request with the expected
scopeandresource=https://drivemcp.googleapis.com/mcp/v1. After completing Google consent, the CLI fails:Google's localhost authorization callback omits
iss. I also confirmed that addingissto the configured redirect URI is not viable: Google rejects it withInvalid redirect_uri contains reserved response param iss.This appears to be the same regression, now blocking the current Google Workspace MCP Developer Preview endpoints as well.
@stevenlee-oai do you maybe have some info on when we can expect this issue to be resolved?
same on ubuntu codex-cli 0.144.6
Data point that should help narrow this down: our server sends the iss parameter correctly, and Codex 0.143.0+ still rejects it. Most reports here are against servers that omit iss — this is a server that includes it and still fails, so the fix likely needs to cover both.
Captured callback from a live authorization:
/callback?code=trc_…&iss=https%3A%2F%2Foauth.example.com&state=…
Discovery also advertises "authorization_response_iss_parameter_supported": true.
Codex 0.141.0 connects fine to the exact same endpoint; 0.143.0+ fails — which points to a client-side change between those versions rather than anything server-side.
This doesn't need to be narrowed down any further. The solution is known, Codex team just needs to land it and cut a release please.
Seeing this issue as well. The bug is squarely on codex's side, the fix is small and simple. Please take a look openAI team
Our organization just got bit by this bug as well. We coded a special workaround but would obviously prefer the fix.
Confirmed against mcp-sso (an open-source OAuth 2.1 / SSO layer for MCP servers) on Codex CLI 0.144.1.
mcp-sso is fully RFC 9207-compliant — it advertises
authorization_response_iss_parameter_supported: trueand returns a correctisson the authorization response. I captured the exact redirect Codex's loopback listener receives:issis present and decodes to precisely the issuer Codex reports as "expected," yet Codex still throwsAuthorization server response missing required issuer— same root cause already diagnosed above (parse_oauth_callbackdiscardsiss;handle_callbackforwardsNone).The notable part for prioritization is the blast radius: this isn't a quirk of one AS — mcp-sso is a published library, and the failure reproduces against it the same way it does against n8n, Better Auth, node-oidc-provider, etc. Every RFC 9207-compliant OAuth front for an MCP server is blocked, and the only server-side workaround is omitting the flag entirely (i.e. disabling RFC 9207 mix-up protection) — not a real option for a security library. Reconfirming to add that signal; happy to serve as a clean, spec-correct repro target.
I can confirm 0.145.0 has the same regression whereas 0.141.0 is RFC 9207 compliant. I await the resolution of this issue.
I’m hitting this with Render’s official hosted MCP server too.
My setup:
codex-cli 0.145.0installed through Homebrewhttps://mcp.render.com/mcpRender’s OAuth metadata advertises:
issuer: https://api.render.comauthorization_response_iss_parameter_supported: truehttps://api.render.com/.well-known/oauth-authorization-server
The authorization URL correctly uses
client_id=codex, but after approving access in the browser,Codex fails with:
``
text
``Authorization server response missing required issuer:
expected https://api.render.com
I also ruled out Render simply omitting the issuer. I sent a test callback to Codex’s localhost
listener that explicitly included
iss=https://api.render.com, and Codex still returned the sameerror before token exchange.
That seems consistent with the diagnosis above: Codex receives the correct issuer but drops it while
handling the callback. As a result, Render’s documented OAuth setup for Codex currently doesn’t work,
leaving API-key authentication as the practical fallback.
Can we please get this fixed?
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.
Anybody have tips on getting this merged, or know who to contact?
The fix is in 0.147.0-alpha.1
Verified the fix works. There's still a regression with claim handling vs 142 but that's a separate issue and not blocking. Thanks!
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 😆
When is this coming to the App?
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.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: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.
I confirm it is fixed in
0.147.0, tested with Fakturoid MCP: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.