ChatGPT Login and Auth
Before codex can run a model it needs OpenAI credentials. This layer handles everything between "user types codex login" and "auth.json is written to $CODEX_HOME": browser OAuth, PKCE, device code, API key, and access token — five entry points share the same CodexAuth enum and AuthManager state machine. The login/ crate handles the pure auth protocol; cli/src/login.rs persists the protocol's output to disk and prints stderr feedback.
Responsibilities
- Unify multiple credential forms: the
CodexAuthenum covers API key, ChatGPT OAuth, external ChatGPT tokens, agent identity JWT, personal access token (PAT), and Bedrock API key — seven modes.codex-rs/login/src/auth/manager.rs:71-79 - Browser OAuth + PKCE:
run_login_serverstarts a local callback server, generates a PKCE pair and state, builds anauth_urlfor the browser to navigate to, and waits for the callback to hand back a code that is then exchanged for a token.codex-rs/login/src/server.rs:151-197 - Device code flow:
run_device_code_loginfirst requests a user_code from/deviceauth/usercode, prints the verification_url in the terminal for the user to enter in a browser, and then polls the token endpoint.codex-rs/login/src/device_code_auth.rs:234-238 - CLI entry wrappers:
run_login_with_chatgpt/run_login_with_api_key/run_login_with_access_token/run_login_with_device_codeclear old credentials before running, install file logging, validate the path againstforced_login_method, andstd::process::exiton completion.codex-rs/cli/src/login.rs:167-196 - Auth reload and revocation:
AuthManager::reloadre-readsauth.jsonafter an external process refreshes the token;logout_with_revokecalls the OAuth revoke endpoint and then clears local credentials.codex-rs/login/src/auth/manager.rs:877-925
Design motivation
codex originally supported only ChatGPT browser login and API key. ChatGPT login goes through OAuth + PKCE: start a local callback server, generate code_verifier / code_challenge, jump to the browser, OpenAI calls back http://localhost:PORT/auth/callback?code=...&state=..., the server uses state for CSRF protection and exchanges code_verifier for a token. This flow requires a browser on the local machine; SSH-remote environments are out of luck.
So a device code flow was added (device_code_auth.rs): the user sees https://chatgpt.com/device and a user_code in the terminal, and enters them in any browser. run_login_with_device_code_fallback_to_browser adds a fallback: try device code first, and if the server returns 404 (device auth not enabled on the backend), fall back to the browser server flow.
CodexAuth is an enum rather than a trait object, because the data shapes of the credential modes differ wildly — an API key is one string, ChatGPT OAuth is an access_token / refresh_token / id_token triple, agent identity is a JWT. An enum makes matches like auth_mode() instantly readable, and adding a new mode is adding a variant rather than rewriting a vtable.
The two policies AgentIdentityAuthPolicy::JwtOnly vs ChatGptAuth decide "when the user is already logged in with ChatGPT, whether to silently register an agent identity". The default ChatGptAuth allows this implicit upgrade, so long-running tasks don't need a separate agent identity login.
Key files
codex-rs/login/src/auth/manager.rs:71-79 — pub enum CodexAuth, the root of the seven credential modes.codex-rs/login/src/auth/manager.rs:929-977 — login_with_access_token, dispatching by token form into PAT or agent identity JWT.codex-rs/login/src/server.rs:68-148 — ServerOptions / LoginServer / ShutdownHandle, the config and lifecycle of the browser OAuth server.codex-rs/login/src/device_code_auth.rs:20-34 — the DeviceCode struct, holding verification_url, user_code, device_auth_id, polling interval.codex-rs/cli/src/login.rs:52-111 — init_login_file_logging, installing a minimal file-backed tracing layer that writes codex-login.log; deliberately not reusing the TUI's heavier telemetry stack.codex-rs/cli/src/login.rs:119-135 — clear_existing_auth_before_login, logging out first before logging in to avoid stale token residue.codex-rs/tui/src/local_chatgpt_auth.rs:17-59 — load_local_chatgpt_auth, a TUI test helper that extracts the ChatGPT access_token / account_id / plan_type from local auth.json.CodexAuth is the single source of truth for credentials; subsequent client.rs, provider, and telemetry all match against it for the mode. Adding a new credential type is adding a variant plus a match arm everywhere.
// login/src/auth/manager.rs:71-79 — 七种凭据模式
pub enum CodexAuth {
ApiKey(ApiKeyAuth),
Chatgpt(ChatgptAuth),
ChatgptAuthTokens(ChatgptAuthTokens),
Headers(AuthHeaders),
AgentIdentity(AgentIdentityAuth),
PersonalAccessToken(PersonalAccessTokenAuth),
BedrockApiKey(BedrockApiKeyAuth),
}run_login_with_chatgpt is a thin CLI-layer wrapper: load config → install file logging → check forced_login_method for whether the ChatGPT path is allowed → clear old credentials → start the callback server. Note it returns ! (never); all exits are process::exit, so callers cannot keep running.
// cli/src/login.rs:167-184 — ChatGPT 登录入口,薄包装 + 硬退出
pub async fn run_login_with_chatgpt(cli_config_overrides: CliConfigOverrides) -> ! {
let config = load_config_or_exit(cli_config_overrides).await;
let _login_log_guard = init_login_file_logging(&config);
tracing::info!("starting browser login flow");
if matches!(config.forced_login_method, Some(ForcedLoginMethod::Api)) {
eprintln!("{CHATGPT_LOGIN_DISABLED_MESSAGE}");
std::process::exit(1);
}
let forced_chatgpt_workspace_id = config.forced_chatgpt_workspace_id.clone();
match login_with_chatgpt(
config.codex_home.to_path_buf(),
forced_chatgpt_workspace_id,
config.cli_auth_credentials_store_mode,
config.auth_keyring_backend_kind(),
config.auth_route_config(),
).await { /* Ok/Err 分别 exit */ }
}login_with_access_token is a good example of "auto-dispatch by token form": the same --with-access-token flag comes in, but internally the JWT structure decides whether to go the PAT path or the agent identity JWT path. When persisting AuthDotJson, the auth_mode field also differs, so that older codex versions can still deserialize correctly on rollback.
// login/src/auth/manager.rs:929-970 — access token 自动分派 PAT vs AgentIdentity
pub async fn login_with_access_token(/* ... */) -> std::io::Result<()> {
let auth_dot_json = match classify_codex_access_token(access_token) {
CodexAccessToken::PersonalAccessToken(access_token) => {
let auth = PersonalAccessTokenAuth::load(access_token, auth_route_config).await?;
ensure_personal_access_token_workspace_allowed(forced_chatgpt_workspace_id, &auth)?;
AuthDotJson { auth_mode: None, personal_access_token: Some(access_token.to_string()), .. }
}
CodexAccessToken::AgentIdentityJwt(jwt) => {
verified_record_from_jwt(jwt, &base_url, auth_route_config).await?;
AuthDotJson { auth_mode: Some(AuthMode::AgentIdentity),
agent_identity: Some(AgentIdentityStorage::Jwt(jwt.to_string())), .. }
}
};
save_auth(codex_home, &auth_dot_json, auth_credentials_store_mode, keyring_backend_kind)
}Data flow
Boundaries and failures
forced_login_methodis mutually exclusive: admins can force only API or ChatGPT; the CLI errors to stderr and exits 1 on mismatch, rather than waiting until the middle of OAuth to be rejected.codex-rs/cli/src/login.rs:172-175- Fallback when device code is unsupported:
run_login_with_device_code_fallback_to_browserruns device code first; if it returnsErrorKind::NotFound(backend hasn't enabled device auth), it auto-falls back to the browser server flow; other errors still surface as failures.codex-rs/cli/src/login.rs:389-419 auth.jsonfile permissions: on Unix,OpenOptionsExt::mode(0o600)forces owner-only read, so credentials are not visible to group users.codex-rs/cli/src/login.rs:69-78- PAT workspace restriction:
ensure_personal_access_token_workspace_allowedvalidatesforced_chatgpt_workspace_idon PAT login, preventing cross-workspace misuse of a PAT.codex-rs/login/src/auth/manager.rs:979-985
Summary
The login/ crate unifies five login entry points into the CodexAuth enum and AuthManager state machine: browser OAuth + PKCE for local use, device code for remote use, API key / PAT / agent identity JWT for automation. cli/src/login.rs is a thin wrapper that installs file logging, clears old credentials, and gates by forced_login_method; all exits are process::exit. Once persisted, auth.json is the credential source for all subsequent model requests; see LLM client and Responses API. After login the user enters the TUI or runs codex exec; see CLI entry and subcommand dispatch.