Skip to content

OAuth authentication fails at issuer validation #31573

Open

Description

@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 modelcontextprotocol/rust-sdk#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

Activity

  1. added
    CLIIssues related to the Codex CLI
    mcpIssues related to the use of model context protocol (MCP) servers
    authIssues related to authentication and accounts
    on Jul 8, 2026
  2. fatalist commented on Jul 9, 2026

    @fatalist

    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:

    http://127.0.0.1:PORT/callback/<callback_id>?code=<long-code>&state=<state>&iss=https%3A%2F%2Fapi.example.com
    

    iss=https://api.example.com is present and equal to the discovery issuer. Codex throws it away before validation.

    Root cause — codex-rs/rmcp-client/src/perform_oauth_login.rs

    (commit 2c85975)

    1. parse_oauth_callback() only captures code / state / error / error_description; the iss param falls into the catch-all and is dropped:

      match key {
          "code" => code = Some(decoded),
          "state" => state = Some(decoded),
          "error" => error = Some(decoded),
          "error_description" => error_description = Some(decoded),
          _ => {}   // <-- `iss` discarded here
      }

      OauthCallbackResult { code, state } has no field to carry it.

    2. 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);
    • 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) with received_issuer = None and require_issuer = true returns AuthError::AuthorizationServerMissingIssuer (≈ L1544-1548).

    So the RFC 9207 check rmcp added is correct; the value simply never reaches it because Codex didn't forward it.

    The fix — rmcp already provides the right entry points

    OAuthState exposes 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 via AuthorizationCallback::from_redirect_url, which does extract iss (≈ L2697-2707)

    Option A (minimal): capture iss in parse_oauth_callback and thread it through:

    // parse_oauth_callback:
    "iss" => issuer = Some(decoded),
    // OauthCallbackResult { code, state, issuer: Option<String> }
    
    // finish():
    self.oauth_state
        .handle_callback_with_issuer(&code, &csrf_state, issuer.as_deref())
        .await
        .context("failed to handle OAuth callback")?;

    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."

  3. tobernguyen commented on Jul 9, 2026

    @tobernguyen

    Confirmed on 0.143.0 and 0.144.0 — the iss is 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) and received_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:

    • AS metadata: { "issuer": "http://127.0.0.1:9700", "authorization_endpoint": ".../authorize", "token_endpoint": ".../token", "authorization_response_iss_parameter_supported": true }
    • /authorize → 302 to the loopback redirect_uri with ?code=...&state=<echoed>&iss=http%3A%2F%2F127.0.0.1%3A9700
    codex mcp add stub --url http://127.0.0.1:9700/mcp --oauth-client-id stub-client
    codex mcp login stub
    # Error: ... Authorization server response missing required issuer: expected http://127.0.0.1:9700
    

    The redirect Codex receives demonstrably contains iss:

    302 http://127.0.0.1:51137/callback/...?code=stub-auth-code&state=...&iss=http%3A%2F%2F127.0.0.1%3A9700
    

    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.

  4. CaliLuke commented on Jul 10, 2026

    @CaliLuke

    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):

    http://127.0.0.1:64626/callback/<redacted>?code=<redacted>&state=<redacted>&iss=https%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth
    

    Codex nevertheless failed with:

    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:

    1. Add issuer: Option<String> to OauthCallbackResult.
    2. Parse the iss query parameter (including percent decoding).
    3. Call handle_callback_with_issuer(&code, &csrf_state, issuer.as_deref()).
    4. 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.

  5. evannotfound commented on Jul 12, 2026

    @evannotfound

    Reproduced with codex-cli 0.144.0-alpha.4, using the binary bundled with ChatGPT.app on macOS:

    /Applications/ChatGPT.app/Contents/Resources/codex mcp login <server>

    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.

  6. microHoffman commented on Jul 13, 2026

    @microHoffman

    Same issue happening on 0.144.3 on Linux.

  7. aditya-findem commented on Jul 14, 2026

    @aditya-findem

    Same issue on 0.144.3.
    Downgrading to 0.141.0 works.

  8. antoniocasagrande-airia commented on Jul 14, 2026

    @antoniocasagrande-airia

    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.

  9. batmanwarrior commented on Jul 14, 2026

    @batmanwarrior

    Same issue on 0.144.4

  10. aditya-findem commented on Jul 14, 2026

    @aditya-findem

    Workaround for anyone/any agents reading this:

    Temporary workaround for npm-installed Codex CLI users:

    npm install -g @openai/codex@0.141.0
    hash -r
    codex --version
    
    codex mcp login <mcp-server-name> \
      --scopes openid,profile,offline_access,mcp:read,mcp:call

    This avoids the OAuth callback regression in Codex 0.143.x–0.144.3. Upgrade again once the issue is fixed.

  11. Lyhtande commented on Jul 15, 2026

    @Lyhtande

    Independent reproduction with n8n's official instance-level MCP server.

    Environment

    • Windows 11 x64
    • Codex Desktop build: 26.707.72221
    • Bundled Codex CLI: 0.144.2
    • MCP transport: Streamable HTTP
    • MCP server: official n8n instance-level MCP at https://<redacted-n8n-host>/mcp-server/http
    • n8n: self-hosted 2.30.1, behind a reverse proxy

    Authorization-server metadata

    GET https://<redacted-n8n-host>/.well-known/oauth-authorization-server returns 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

    1. Configure the server as a Streamable HTTP MCP server.

    2. Run:

      codex mcp login n8n_test
      
    3. Complete n8n's consent page. Dynamic client registration and consent succeed, and n8n lists Codex under connected OAuth clients.

    4. n8n redirects to Codex's loopback callback, which displays:

      Authentication complete. You may close this window.
      
    5. The CLI then fails with:

      Error: failed to handle OAuth callback
      
      Caused by:
          Authorization server response missing required issuer: expected https://<redacted-n8n-host>
      
    6. 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:

    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.

  12. 16 remaining items

  13. warbadata commented on Jul 28, 2026

    @warbadata

    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.

  14. emlazzarin commented on Jul 28, 2026

    @emlazzarin

    Anybody have tips on getting this merged, or know who to contact?

  15. emlazzarin commented on Jul 29, 2026

    @emlazzarin

    The fix is in 0.147.0-alpha.1

  16. warbadata commented on Jul 30, 2026

    @warbadata

    Verified the fix works. There's still a regression with claim handling vs 142 but that's a separate issue and not blocking. Thanks!

  17. kashishhora commented on Jul 30, 2026

    @kashishhora

    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 😆

  18. domi-zabe-scacap commented on Jul 31, 2026

    @domi-zabe-scacap

    When is this coming to the App?

  19. 0xdevalias commented on Aug 7, 2026

    @0xdevalias

    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 app Version 26.730.61639 (Released Aug 5, 2026) on macOS Tahoe 26.6 (25G72) seemed to work.

  20. jgennari commented on Aug 7, 2026

    @jgennari

    Confirmed fixed for me in stable Codex CLI 0.147.0 using 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, and search_email calls. No older-CLI workaround was needed.

    This appears resolved by #35720.

  21. iliferov commented on Aug 8, 2026

    @iliferov

    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.
    
  22. naipi11 commented on Aug 18, 2026

    @naipi11

    Quick heads-up: this OAuth issuer-validation regression is fixed in stable CLI 0.147.0.

    Method:

    1. Upgrade Codex CLI to 0.147.0 or newer (codex update, or reinstall).
    2. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    CLIIssues related to the Codex CLIauthIssues related to authentication and accountsbugSomething isn't workingmcpIssues related to the use of model context protocol (MCP) servers

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions