openai / openai/codex

MCP OAuth: DCR client_secret is not persisted across restarts, causing refresh failures

Open
#43,216 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

auth bug CLI mcp
Dominant language
Rust
Stars
125k
Forks
19.4k
PR merge metrics
PR metrics pending

Description

What version of Codex CLI is running?

Investigation: official codex-cli 0.153.4; desktop-bundled Codex 0.152.1 before a subsequent app update. Post-update refresh not revalidated.

What subscription do you have?

Not verified for this report

Which model were you using?

Not model-specific: MCP OAuth refresh / initialization

What platform is your computer?

macOS 26.4.1 (25E253), Apple Silicon

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

No response

Codex doctor report
Not collected for this report. No unreviewed diagnostic dump is attached.
What issue are you seeing?
Summary

Codex's MCP OAuth credential persistence path does not retain the client_secret issued during dynamic client registration (DCR) in the inspected revision. A later process therefore cannot restore the same client authentication material for token refresh.

This is a client-side persistence/restoration gap for DCR clients whose authorization server requires that secret at the token endpoint. It is not inherently specific to one MCP service. However, the real-provider evidence below is limited to Supabase; other providers and public clients that do not require a secret have NOT been shown to be affected.

Codex-side evidence

Source inspected: rust-v0.153.4, commit 3d2ee51ca2d5db578f328aa75e20aa22c0197c9a; dependencies rmcp 3.1.3 and oauth2 5.0.0.

  1. RMCP retains the DCR-issued secret in its OAuth client configuration for the initial authorization-code exchange.
  2. Codex persists client_id and token_response, but StoredOAuthTokens has no client_secret field.
  3. On restoration, the OAuth client is initialized without the DCR secret. This loses client authentication material even though the token credentials themselves were stored.

Pinned source references:

Observed example: hosted Supabase MCP

An actual DCR response contained a nonempty client_secret (value not logged), while the affected existing macOS Keychain credential lacked that field. Login initially succeeded, but later official-client refresh attempts produced:

OAuth token refresh failed: Failed to parse server response

A controlled refresh request using an expired affected credential captured:

HTTP 422
{"message":"Required parameter: client_secret"}

This rejection is consistent with the missing client authentication material. The HTTP status and body above are specific to this provider observation, not claimed as a universal response. Its body contains message rather than the standard OAuth error field expected by the decoder, so the generic parse error also hides the underlying rejection.

What steps can reproduce the bug?
Relevant conditions

The MCP OAuth flow uses DCR, the authorization server issues a client secret and requires it for refresh, and a new Codex process restores the saved credentials. This is the failure scope inferred from the inspected client code; cross-provider reproduction remains unverified.

Reproduction outline using the observed provider
  1. Configure a project-scoped hosted Supabase remote MCP endpoint using the actual URL locally.
  2. Run codex mcp login <server-name> with the official CLI and complete browser authorization.
  3. Exit the client process.
  4. After access-token expiry, start a fresh client process and use the MCP server.
  5. Observe the refresh parse error. For diagnosis, inspect sanitized HTTP status and field-presence information only; do not log credentials.

The observed provider returned expires_in: 86400. A newly instrumented natural 24-hour expiry observation was NOT completed. The evidence is the repeated official-client failures, controlled provider rejection, and source/storage inspection; the outline above is not a claim of a completed fresh 24-hour end-to-end test.

What is the expected behavior?

Codex should securely persist and restore the client authentication material required by the registered OAuth client, including a DCR-issued secret when applicable, alongside the token credentials. Restoration should preserve issuer/resource binding and the applicable client authentication method. A process restart should not lose material required to refresh an otherwise refreshable session.

This does not mean requiring secrets for public clients that do not use them.

Separately, if an OAuth error response cannot be decoded, Codex should expose a sanitized HTTP status/provider error classification rather than only a generic parse error, without logging credentials or successful token responses.

Additional information
Scope and related report

The primary issue is Codex's provider-independent DCR credential persistence/restoration path. Supabase is the concrete observed example, not a claim that the defect belongs to Supabase or that every MCP provider is affected.

Related: #13852 reports the same user-visible refresh parse error and repeated reauthentication with Supabase. This report focuses on the specific client-secret persistence gap and captured rejection. It does not establish that every case in #13852 shares this cause.

Supporting experiment, not an official fix

An isolated experimental build retaining and restoring the DCR secret completed two real Supabase provider refreshes, each followed by a fresh process and a read-only get_project_url MCP call. Only the isolated store's local expiry hint was changed to trigger these checks. This supports the proposed cause in that tested case, but is NOT a natural 24-hour expiry test, an official fix, or a supported workaround. The experiment has been abandoned.

Version and validation limits

The investigation used official CLI 0.153.4 and a desktop-bundled Codex 0.152.1 environment. The desktop app was subsequently updated, resolving an unrelated computer-use configuration-loading problem. MCP OAuth refresh has NOT been revalidated after that update. No claim is made that the updated app is fixed or still affected, or that other providers were tested.

No access tokens, refresh tokens, client secrets, client IDs, project refs, account identifiers, or authorization URLs are included. Please confirm whether a newer official release addresses this DCR persistence gap.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with codex-rs/rmcp-client/src/oauth.rs and codex-rs/rmcp-client/src/perform_oauth_login.rs at the pinned references, then trace StoredOAuthTokens through persistence and restoration. Done means a DCR-issued client secret is retained and restored when applicable, preserving refresh across a fresh process without logging credentials; also inspect how undecodable OAuth errors are surfaced.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
authentication, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.