MCP OAuth rejects Microsoft Entra multi-tenant issuer after successful authentication

Open 💬 2 comments Opened Aug 17, 2026 by johnt7566
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What version of Codex CLI is running?

0.148.0-alpha.9

What subscription do you have?

ChatGPT Business

Which model were you using?

Not applicable. The failure occurs during MCP OAuth authentication, before any MCP tools become available.

What platform is your computer?

Windows x64

What terminal emulator and version are you using (if applicable)?

PowerShell, launched from Codex Desktop.

Codex doctor report

{
  "schemaVersion": 1,
  "generatedAt": "1786958873s since unix epoch",
  "overallStatus": "ok",
  "codexVersion": "0.147.0",
  "checks": {
    "app_server.status": {
      "id": "app_server.status",
      "category": "app-server",
      "status": "ok",
      "summary": "background server is not running",
      "details": {
        "control socket": "C:\\Users\\John.Thornton\\.codex\\app-server-control\\app-server-control.sock",
        "daemon state dir": "C:\\Users\\John.Thornton\\.codex\\app-server-daemon",
        "mode": "ephemeral",
        "pid file": "C:\\Users\\John.Thornton\\.codex\\app-server-daemon\\app-server.pid (missing)",
        "settings": "C:\\Users\\John.Thornton\\.codex\\app-server-daemon\\settings.json (missing)",
        "status": "not running",
        "update-loop pid file": "C:\\Users\\John.Thornton\\.codex\\app-server-daemon\\app-server-updater.pid (missing)"
      },
      "remediation": null,
      "durationMs": 0
    },
    "auth.credentials": {
      "id": "auth.credentials",
      "category": "auth",
      "status": "ok",
      "summary": "auth is configured",
      "details": {
        "auth file": "C:\\Users\\John.Thornton\\.codex\\auth.json",
        "auth storage mode": "File",
        "stored API key": "false",
        "stored ChatGPT tokens": "true",
        "stored agent identity": "false",
        "stored auth mode": "chatgpt"
      },
      "remediation": null,
      "durationMs": 0
    },
    "config.load": {
      "id": "config.load",
      "category": "config",
      "status": "ok",
      "summary": "config loaded",
      "details": {
        "CODEX_HOME": "C:\\Users\\John.Thornton\\.codex",
        "config.toml": "C:\\Users\\John.Thornton\\.codex\\config.toml",
        "config.toml parse": "ok",
        "cwd": "C:\\Users\\John.Thornton",
        "enabled feature flags": "<redacted>",
        "feature flag overrides": "none",
        "feature flags enabled": "39",
        "log dir": "C:\\Users\\John.Thornton\\.codex\\log",
        "mcp servers": "4",
        "model": "gpt-5.6-sol",
        "model provider": "openai",
        "sqlite home": "C:\\Users\\John.Thornton\\.codex"
      },
      "remediation": null,
      "durationMs": 0
    },
    "git.environment": {
      "id": "git.environment",
      "category": "git",
      "status": "ok",
      "summary": "git version 2.53.0.windows.3",
      "details": {
        "PATH git #1": "C:\\Users\\John.Thornton\\.cache\\codex-runtimes\\codex-primary-runtime\\dependencies\\native\\git\\cmd\\git.exe",
        "PATH git entries": "1",
        "git build options": "git version 2.53.0.windows.3; cpu: x86_64; built from commit: f8165afd89b0c190677a093f20894f5fce12f97a; sizeof-long: 4; sizeof-size_t: 8; shell-path: D:/git-sdk-64/usr/bin/sh; rust: disabled; feature: fsmonitor--daemon; gettext: enabled; libcurl: 8.18.0; OpenSSL: OpenSSL 3.5.5 27 Jan 2026; zlib: 1.3.1; SHA-1: SHA1_DC; SHA-256: SHA256_BLK; default-ref-format: files; default-hash: sha1",
        "git exec path": "C:/Users/John.Thornton/.cache/codex-runtimes/codex-primary-runtime/dependencies/native/git/mingw64/libexec/git-core",
        "git version": "git version 2.53.0.windows.3",
        "repo detected": "false",
        "selected git": "C:\\Users\\John.Thornton\\.cache\\codex-runtimes\\codex-primary-runtime\\dependencies\\native\\git\\cmd\\git.exe"
      },
      "remediation": null,
      "durationMs": 183
    },
    "installation": {
      "id": "installation",
      "category": "install",
      "status": "ok",
      "summary": "installation looks consistent",
      "details": {
        "PATH codex #1": "C:\\Users\\John.Thornton\\AppData\\Roaming\\npm\\codex",
        "PATH codex #2": "C:\\Users\\John.Thornton\\AppData\\Roaming\\npm\\codex.cmd",
        "PATH codex entries": "2",
        "current executable": "C:\\Users\\John.Thornton\\AppData\\Roaming\\npm\\node_modules\\@openai\\codex\\node_modules\\@openai\\codex-win32-x64\\vendor\\x86_64-pc-windows-msvc\\bin\\codex.exe",
        "install context": "npm (package C:\\Users\\John.Thornton\\AppData\\Roaming\\npm\\node_modules\\@openai\\codex\\node_modules\\@openai\\codex-win32-x64\\vendor\\x86_64-pc-windows-msvc, bin C:\\Users\\John.Thornton\\AppData\\Roaming\\npm\\node_modules\\@openai\\codex\\node_modules\\@openai\\codex-win32-x64\\vendor\\x86_64-pc-windows-msvc\\bin, resources C:\\Users\\John.Thornton\\AppData\\Roaming\\npm\\node_modules\\@openai\\codex\\node_modules\\@openai\\codex-win32-x64\\vendor\\x86_64-pc-windows-msvc\\codex-resources, path C:\\Users\\John.Thornton\\AppData\\Roaming\\npm\\node_modules\\@openai\\codex\\node_modules\\@openai\\codex-win32-x64\\vendor\\x86_64-pc-windows-msvc\\codex-path)",
        "managed by bun": "false",
        "managed by npm": "true",
        "managed by pnpm": "false",
        "managed package root": "C:\\Users\\John.Thornton\\AppData\\Roaming\\npm\\node_modules\\@openai\\codex",
        "npm update target": "C:\\Users\\John.Thornton\\AppData\\Roaming\\npm\\node_modules\\@openai\\codex"
      },
      "remediation": null,
      "durationMs": 1205
    },
    "mcp.config": {
      "id": "mcp.config",
      "category": "mcp",
      "status": "ok",
      "summary": "MCP configuration is locally consistent",
      "details": {
        "configured servers": "4",
        "disabled servers": "0",
        "stdio servers": "1",
        "streamable_http servers": "3"
      },
      "remediation": null,
      "durationMs": 537
    },
    "network.env": {
      "id": "network.env",
      "category": "network",
      "status": "ok",
      "summary": "network-related environment looks readable",
      "details": {
        "proxy env vars": "none"
      },
      "remediation": null,
      "durationMs": 0
    },
    "network.provider_reachability": {
      "id": "network.provider_reachability",
      "category": "reachability",
      "status": "ok",
      "summary": "active provider endpoints are reachable over HTTP",
      "details": {
        "ChatGPT base URL": "https://chatgpt.com/backend-api/ reachable (HTTP 403)",
        "reachability mode": "ChatGPT auth"
      },
      "remediation": null,
      "durationMs": 122
    },
    "network.websocket_reachability": {
      "id": "network.websocket_reachability",
      "category": "websocket",
      "status": "ok",
      "summary": "Responses WebSocket handshake succeeded",
      "details": {
        "DNS": "2 IPv4, 2 IPv6, first IPv6",
        "auth mode": "chatgpt",
        "connect timeout": "15000 ms",
        "endpoint": "wss://chatgpt.com/backend-api/<redacted>",
        "handshake result": "HTTP 101 Switching Protocols",
        "model provider": "openai",
        "models etag present": "true",
        "provider name": "OpenAI",
        "proxy env vars": "none",
        "reasoning header": "false",
        "server model present": "false",
        "supports websockets": "true",
        "wire API": "responses"
      },
      "remediation": null,
      "durationMs": 647
    },
    "runtime.provenance": {
      "id": "runtime.provenance",
      "category": "runtime",
      "status": "ok",
      "summary": "running npm on windows-x86_64",
      "details": {
        "commit": "unknown",
        "current executable": "C:\\Users\\John.Thornton\\AppData\\Roaming\\npm\\node_modules\\@openai\\codex\\node_modules\\@openai\\codex-win32-x64\\vendor\\x86_64-pc-windows-msvc\\bin\\codex.exe",
        "install method": "npm (package C:\\Users\\John.Thornton\\AppData\\Roaming\\npm\\node_modules\\@openai\\codex\\node_modules\\@openai\\codex-win32-x64\\vendor\\x86_64-pc-windows-msvc, bin C:\\Users\\John.Thornton\\AppData\\Roaming\\npm\\node_modules\\@openai\\codex\\node_modules\\@openai\\codex-win32-x64\\vendor\\x86_64-pc-windows-msvc\\bin, resources C:\\Users\\John.Thornton\\AppData\\Roaming\\npm\\node_modules\\@openai\\codex\\node_modules\\@openai\\codex-win32-x64\\vendor\\x86_64-pc-windows-msvc\\codex-resources, path C:\\Users\\John.Thornton\\AppData\\Roaming\\npm\\node_modules\\@openai\\codex\\node_modules\\@openai\\codex-win32-x64\\vendor\\x86_64-pc-windows-msvc\\codex-path)",
        "platform": "windows-x86_64",
        "version": "0.147.0"
      },
      "remediation": null,
      "durationMs": 0
    },
    "runtime.search": {
      "id": "runtime.search",
      "category": "search",
      "status": "ok",
      "summary": "search is OK (bundled)",
      "details": {
        "search command": "C:\\Users\\John.Thornton\\AppData\\Roaming\\npm\\node_modules\\@openai\\codex\\node_modules\\@openai\\codex-win32-x64\\vendor\\x86_64-pc-windows-msvc\\codex-path\\rg.exe",
        "search command readiness": "file exists",
        "search provider": "bundled"
      },
      "remediation": null,
      "durationMs": 0
    },
    "sandbox.helpers": {
      "id": "sandbox.helpers",
      "category": "sandbox",
      "status": "ok",
      "summary": "sandbox configuration is readable",
      "details": {
        "approval policy": "OnRequest",
        "codex-linux-sandbox helper": "none",
        "execve wrapper helper": "none",
        "filesystem sandbox": "restricted",
        "network sandbox": "restricted"
      },
      "remediation": null,
      "durationMs": 0
    },
    "state.paths": {
      "id": "state.paths",
      "category": "state",
      "status": "ok",
      "summary": "state paths and databases are inspectable",
      "details": {
        "CODEX_HOME": "C:\\Users\\John.Thornton\\.codex (dir)",
        "active rollout files": "59 files, 100633384 total bytes, 1705650 average bytes",
        "archived rollout files": "0 files, 0 total bytes, 0 average bytes",
        "goals DB": "C:\\Users\\John.Thornton\\.codex\\goals_1.sqlite (file)",
        "goals DB integrity": "ok",
        "log DB": "C:\\Users\\John.Thornton\\.codex\\logs_2.sqlite (file)",
        "log DB integrity": "ok",
        "log dir": "C:\\Users\\John.Thornton\\.codex\\log (missing)",
        "memories DB": "C:\\Users\\John.Thornton\\.codex\\memories_1.sqlite (file)",
        "memories DB integrity": "ok",
        "queue DB": "C:\\Users\\John.Thornton\\.codex\\queue_1.sqlite (file)",
        "queue DB integrity": "ok",
        "sqlite home": "C:\\Users\\John.Thornton\\.codex (dir)",
        "state DB": "C:\\Users\\John.Thornton\\.codex\\state_5.sqlite (file)",
        "state DB integrity": "ok",
        "thread history DB": "C:\\Users\\John.Thornton\\.codex\\thread_history_1.sqlite (missing)",
        "thread history DB integrity": "skipped (missing)"
      },
      "remediation": null,
      "durationMs": 456
    },
    "state.rollout_db_parity": {
      "id": "state.rollout_db_parity",
      "category": "threads",
      "status": "ok",
      "summary": "rollout files and state DB thread inventory agree",
      "details": {
        "default model provider": "openai",
        "rollout DB active files": "59",
        "rollout DB active rows": "59",
        "rollout DB archive mismatches": "0",
        "rollout DB archived files": "0",
        "rollout DB archived rows": "0",
        "rollout DB duplicate DB paths": "0",
        "rollout DB duplicate rollout thread ids": "0",
        "rollout DB malformed file names": "0",
        "rollout DB missing active rows": "0",
        "rollout DB missing archived rows": "0",
        "rollout DB model providers": "openai=59",
        "rollout DB rows": "59",
        "rollout DB scan cap reached": "false",
        "rollout DB scan errors": "0",
        "rollout DB sources": "vscode=57, subagent:other=2",
        "rollout DB stale rows": "0"
      },
      "remediation": null,
      "durationMs": 180
    },
    "system.environment": {
      "id": "system.environment",
      "category": "system",
      "status": "ok",
      "summary": "OS language en-GB",
      "details": {
        "EDITOR": "not set",
        "VISUAL": "not set",
        "os": "Windows 10.0.26200 (Windows 11 Professional) [64-bit]",
        "os language": "en-GB",
        "os type": "Windows",
        "os version": "10.0.26200"
      },
      "remediation": null,
      "durationMs": 0
    },
    "terminal.env": {
      "id": "terminal.env",
      "category": "terminal",
      "status": "ok",
      "summary": "terminal metadata was detected",
      "details": {
        "color output": "enabled",
        "console input code page": "850",
        "console output code page": "850",
        "stderr console mode": "0x00000007 (VT processing: true)",
        "stderr is terminal": "true",
        "stdin is terminal": "true",
        "stdout console mode": "0x00000007 (VT processing: true)",
        "stdout is terminal": "true",
        "terminal": "unknown",
        "terminal size": "120x30"
      },
      "remediation": null,
      "durationMs": 1
    },
    "terminal.title": {
      "id": "terminal.title",
      "category": "title",
      "status": "ok",
      "summary": "terminal title default",
      "details": {
        "terminal title activity": "true",
        "terminal title items": "activity, project-name",
        "terminal title project source": "cwd",
        "terminal title project value": "John.Thornton",
        "terminal title source": "default"
      },
      "remediation": null,
      "durationMs": 0
    },
    "updates.status": {
      "id": "updates.status",
      "category": "updates",
      "status": "ok",
      "summary": "update configuration is locally consistent",
      "details": {
        "check for update on startup": "true",
        "latest version": "0.147.0",
        "latest version status": "current version is not older",
        "npm update target": "C:\\Users\\John.Thornton\\AppData\\Roaming\\npm\\node_modules\\@openai\\codex",
        "update action": "npm install -g @openai/codex",
        "version cache": [
          "C:\\Users\\John.Thornton\\.codex\\version.json",
          "missing"
        ]
      },
      "remediation": null,
      "durationMs": 741
    }
  }
}

What issue are you seeing?

Codex cannot complete OAuth authentication for Microsoft Fabric's FabricIQ remote MCP server.

The server advertises the Microsoft Entra multi-tenant authorization-server issuer:

https://login.microsoftonline.com/organizations/v2.0

Microsoft browser authentication completes successfully and redirects back to Codex's localhost callback. Codex then rejects the callback because Microsoft returns a tenant-specific issuer:

https://login.microsoftonline.com/<tenant-id>/v2.0

The resulting error is:

Error: Authorization server issuer mismatch:
expected https://login.microsoftonline.com/organizations/v2.0,
received https://login.microsoftonline.com/<tenant-id>/v2.0

The tenant ID has been redacted.

Because the OAuth result is rejected, the FabricIQ MCP server remains in an authentication-required state and its tools are absent from Codex.

This appears to be a strict issuer-comparison problem in Codex's MCP OAuth implementation. Codex treats the /organizations authority and the resulting tenant-specific Microsoft Entra issuer as unrelated, even though the browser authentication was initiated through that multi-tenant authority.

What steps can reproduce the bug?

  1. Configure the Microsoft Fabric FabricIQ MCP server:

``toml
[mcp_servers.FabricIQ]
url = "https://api.fabric.microsoft.com/v1/mcp/fabricaihub/integrations/m365"
http_headers = { X-VARIANTS = "Fabric.Routing.PowerBIDataExploration" }
``

  1. Start OAuth login using CIMD:

``powershell
codex mcp login FabricIQ --oauth-client-registration cimd
``

  1. Complete the Microsoft browser-based authentication.
  1. The browser returns to the localhost OAuth callback.
  1. Codex fails while handling the callback:

``text
Authorization server issuer mismatch:
expected https://login.microsoftonline.com/organizations/v2.0,
received https://login.microsoftonline.com/<tenant-id>/v2.0
``

  1. Restart or reload Codex. The FabricIQ MCP tools are still unavailable because authentication was not accepted.

What is the expected behavior?

Codex should successfully complete OAuth when Microsoft Entra's /organizations/v2.0 authority produces a valid tenant-specific issuer after the user authenticates.

Possible solutions could include:

  • validating the returned issuer as a tenant-specific instance of the advertised Microsoft Entra multi-tenant authority;
  • supporting Microsoft Entra issuer templates;
  • allowing an explicit tenant-specific authorization-server or issuer override in the MCP configuration; or
  • providing another documented way to configure the expected issuer.

After successful authentication, the FabricIQ MCP server should initialize and expose its tools.

Actual behavior

The browser sign-in succeeds, but Codex rejects the OAuth callback during issuer validation. The token cannot be used and the FabricIQ MCP server's tools remain absent.

Related open issues

These issues appear related, but none describes this exact Microsoft Entra /organizations versus tenant-specific issuer mismatch:

  • https://github.com/openai/codex/issues/31573 — OAuth authentication fails at issuer validation. This concerns a missing RFC 9207 iss callback parameter rather than a Microsoft Entra multi-tenant/tenant-specific mismatch.
  • https://github.com/openai/codex/issues/37373 — MCP app-server strips a trailing slash from the expected authorization-server issuer. This is another strict issuer-comparison failure, but involves URL canonicalization.
  • https://github.com/openai/codex/issues/15818 — Remote HTTP MCP OAuth with Microsoft Entra fails when dynamic client registration is unavailable. It involves the same /organizations/v2.0 authority and remote MCP authentication, but fails during client registration rather than issuer validation.

Additional information

  • Fabric skills plugin version: 0.3.12
  • Power BI authoring plugin version: 0.3.12
  • The FabricIQ endpoint is reachable and OAuth discovery successfully initiates Microsoft authentication.
  • This failure occurs before Power BI/Fabric permissions or report access can be evaluated.
  • No tokens, tenant IDs, account email addresses, or client secrets are included in this report.

View original on GitHub ↗

2 Comments

github-actions[bot] contributor · 11 days ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #38944

Powered by Codex Action

jdcodes1 · 11 days ago

The dedup bot flagged #38944, and the two do share an error message and code path — but they need different fixes, and it's worth keeping that distinction in whatever lands.

Why this is not #38944. In #38944 (Meta), the provider's discovery metadata is self-inconsistent — a static per-server oauth_expected_issuer override fixes it because the correct issuer is one known constant. Here the mismatch is by design on Microsoft's side: Entra's multi-tenant authorities (/organizations, /common, /consumers) publish AS metadata whose issuer is literally a templatehttps://login.microsoftonline.com/{tenantid}/v2.0 — and the concrete issuer is only known after the user authenticates and their home tenant is resolved. That's exactly what the reported callback shows: authority …/organizations/v2.0, received issuer …/<tenant-id>/v2.0. A static expected-issuer override can't express this, because the tenant id isn't known in advance (and for a genuinely multi-tenant server, isn't even unique).

Where the check lives. Same seam as #38944: the strict comparison is in the pinned rmcp crate's AuthorizationManager (rmcp =3.0.0 in codex-rs/Cargo.toml), driven from codex mcp login at:

https://github.com/openai/codex/blob/1f41cc5d92722748e45cae9cecc6d883a4e7cbb1/codex-rs/rmcp-client/src/perform_oauth_login.rs#L699-L707

with the RFC 9207 iss callback parameter plumbed through handle_callback_with_issuer (#L632-L639). Codex's strictness is the RFC-conformant default; Entra multi-tenant is the ecosystem's canonical special case (MSAL and most OIDC stacks handle it via issuer-template validation rather than string equality).

Fix shapes, in order of security soundness:

  1. Template-aware Entra validation: when the configured authority is a Microsoft multi-tenant alias, accept a received issuer that matches the documented template (https://login.microsoftonline.com/<GUID>/v2.0), and — the part that keeps mix-up protection intact — cross-check the <GUID> against the tid claim of the returned token. This is what Microsoft's own guidance for validating multi-tenant issuers amounts to.
  2. Pin-on-first-login: resolve the tenant issuer once at first successful login and store it with the server's credentials; subsequent validations use the pinned tenant issuer with strict equality. Handles the single-org case cleanly and composes with (1).
  3. What to avoid: a bare host-prefix or wildcard comparison. The issuer check is the defense against AS mix-up; relaxing it to "same host" would accept any tenant on login.microsoftonline.com, which is meaningfully weaker than (1)'s template+tid validation.

If #38944's oauth_expected_issuer lands first, one small addition makes it cover this case too: allow the value to be the multi-tenant authority and treat it as engaging behavior (1) rather than strict equality. Either way, the implementation slot is the same AuthorizationManager API boundary flagged in #38944, so it's worth designing the two together even though they're distinct bugs.