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, includingrequest_user_code,poll_for_token, and the orchestration functionrun_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 viaheadless_chatgpt_login.rs, which updates theSignInStateenum 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_codefunction initiates the flow by posting to/deviceauth/usercode, whilepoll_for_tokenhandles the 15-minute polling window to/deviceauth/token. - Token exchange employs PKCE verification via
PkceCodesto securely obtainid_token,access_token, andrefresh_tokencredentials. - Credentials are persisted to
$CODX_HOMEthroughpersist_tokens_async, supporting both encrypted keyring and plaintext storage modes based on user preference. - The terminal UI layer in
headless_chatgpt_login.rsmanages 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →