Codex CLI Authentication: How the Device-Code Flow Works with ChatGPT

The Codex CLI authenticates users via an OAuth 2.0 Device-Code grant implemented in Rust, where the terminal displays a one-time code for browser verification, polls for authorization, and securely persists tokens to the local credential store.

The openai/codex repository implements a secure, headless-friendly login mechanism for the Codex CLI that avoids manual API key entry. Instead of requiring users to paste tokens into the terminal, the Codex CLI authentication system orchestrates a browser-based device-code flow across three distinct architectural layers: the HTTP client library, server-side utilities, and the terminal UI.

The OAuth 2.0 Device-Code Flow Implementation

The authentication sequence is implemented in codex-rs/login/src/device_code_auth.rs and follows the standard device-authorization grant pattern with four distinct stages.

Stage 1: Requesting the Device Code

The flow begins when the CLI calls request_user_code to initiate a session with the Codex account API. This function sends a POST /deviceauth/usercode request to the server, which returns a unique user_code, a device_auth_id, and a polling interval that dictates how frequently the client should check for completion.

According to the source in device_code_auth.rs (lines 62-76), the response includes the verification URL and the temporary device code required for the next steps.

Stage 2: User Verification Prompt

Once the device code is obtained, print_device_code_prompt (lines 49-57) renders the instructions in the terminal. This function displays the one-time code and the verification URL ({issuer}/codex/device), instructing the user to open a browser, navigate to the URL, and enter the displayed code to authorize the CLI session.

Stage 3: Polling for Authorization

While the user completes the browser step, the CLI enters a polling loop via poll_for_token (lines 99-126). This function repeatedly posts to /deviceauth/token at the server-specified interval, respecting rate limits and terminating after a 15-minute timeout. Upon successful authorization, the server responds with an authorization_code, a code_challenge, and a code_verifier required for the final token exchange.

Stage 4: Token Exchange and Persistence

The CLI constructs a PKCE pair using PkceCodes and invokes exchange_code_for_tokens to trade the authorization code for long-lived credentials. This yields an id_token, access_token, and refresh_token. The server utilities in codex-rs/server then perform two final operations: ensure_workspace_allowed validates the workspace permissions, and persist_tokens_async writes the credentials to the CLI’s credential store at $CODX_HOME, respecting the user’s configured cli_auth_credentials_store_mode.

Architecture Overview

The authentication system spans three crates in the codex-rs workspace, each handling a specific concern:

  • codex-rs/login – Contains the pure HTTP implementation of the device-code flow, including request_user_code, poll_for_token, and the orchestration function run_device_code_login (lines 25-31).
  • codex-rs/server – Handles PKCE generation, token exchange, workspace validation, and secure credential persistence to disk or system keyring.
  • codex-rs/tui – Drives the interactive experience via headless_chatgpt_login.rs, which updates the SignInState enum while the async polling runs, allowing users to cancel or view progress animations.

Code Implementation Examples

Stand-Alone Device Code Request

To initiate the flow programmatically without the full TUI:

use codex_login::{request_device_code, run_device_code_login};
use codex_rs::login::ServerOptions;

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let opts = ServerOptions::default();
    
    // Fetch device code from the server
    let device_code = request_device_code(&opts).await?;
    
    println!("Open {} and enter {}", 
        device_code.verification_url, 
        device_code.user_code);
    
    // Poll and persist tokens
    run_device_code_login(opts).await
}

TUI Integration

Inside the terminal UI, the flow is coordinated through headless_chatgpt_login.rs:

// From headless_chatgpt_login::render_device_code_login
let device_code = request_device_code(&opts).await?;
print_device_code_prompt(&device_code.verification_url, &device_code.user_code);

// Update UI state while polling
let _ = complete_device_code_login(opts.clone(), device_code).await?;

Credential Persistence

The final storage operation uses the server utilities:

crate::server::persist_tokens_async(
    &opts.codex_home,          // e.g., $HOME/.codex
    None,                      // default profile
    tokens.id_token,
    tokens.access_token,
    tokens.refresh_token,
    opts.cli_auth_credentials_store_mode,
).await?;

Key Source Files and Functions

File Path Role Key Functions
codex-rs/login/src/device_code_auth.rs Core HTTP flow implementation request_user_code, poll_for_token, run_device_code_login
codex-rs/tui/src/onboarding/auth/headless_chatgpt_login.rs UI state management and user prompts render_device_code_login, complete_device_code_login
codex-rs/tui/src/onboarding/auth.rs Authentication state definitions SignInState enum
codex-rs/server/src/lib.rs PKCE handling and token storage exchange_code_for_tokens, persist_tokens_async
docs/authentication.md High-level CLI authentication documentation

Summary

  • The Codex CLI authentication flow uses the OAuth 2.0 Device-Code grant to enable browser-based login without manual API key entry.
  • The request_user_code function initiates the flow by posting to /deviceauth/usercode, while poll_for_token handles the 15-minute polling window to /deviceauth/token.
  • Token exchange employs PKCE verification via PkceCodes to securely obtain id_token, access_token, and refresh_token credentials.
  • Credentials are persisted to $CODX_HOME through persist_tokens_async, supporting both encrypted keyring and plaintext storage modes based on user preference.
  • The terminal UI layer in headless_chatgpt_login.rs manages user-facing state updates and cancellation handling during the asynchronous authorization wait.

Frequently Asked Questions

Does Codex CLI support API key authentication instead of the device-code flow?

No, as implemented in the current openai/codex source, the CLI exclusively uses the OAuth 2.0 Device-Code flow for ChatGPT authentication. The codebase does not expose a manual API key entry path; all authentication routes through run_device_code_login and the browser-based verification URL.

Where are the authentication tokens stored after a successful login?

The tokens are written to the local credential store via persist_tokens_async in the codex-rs/server crate. By default, this resolves to the $CODX_HOME directory (typically $HOME/.codex), though the exact storage mechanism—encrypted system keyring versus plaintext file—depends on the cli_auth_credentials_store_mode configuration option set during the exchange phase.

What happens if I don't complete the browser verification within 15 minutes?

The poll_for_token function enforces a hard 15-minute timeout while polling /deviceauth/token. If the user does not authorize the device code in the browser within this window, the function returns an error, the CLI aborts the login attempt, and the user must restart the flow by invoking request_user_code again to generate a new one-time code.

Can the device-code authentication be used in headless or CI environments?

While the underlying HTTP implementation in device_code_auth.rs supports headless operation, the standard flow requires a human to open a browser and manually enter the user_code. For fully automated environments, the current implementation would require pre-existing valid tokens in the $CODX_HOME credential store, as there is no non-interactive authentication method exposed in the public API surface.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →