feat: load AgentIdentity from JWT login/env (#18904)

## Summary

This PR lets programmatic AgentIdentity users provide one token through
either stdin login or environment auth.

`codex login --with-agent-identity` reads an Agent Identity JWT from
stdin, validates that it has the required claims, and stores that token
as the `agent_identity` value in `auth.json`. The file format is
token-only; the decoded account and key fields are runtime state, not
hand-authored auth.json fields.

The Agent Identity JWT claim shape and decoder live in
`codex-agent-identity`; `codex-login` only owns env/storage precedence
and conversion into `CodexAuth::AgentIdentity`.

When env auth is enabled, `CODEX_AGENT_IDENTITY` can provide the same
JWT without writing auth state to disk. `CODEX_API_KEY` still wins if
both env vars are set.

Reference old stack: https://github.com/openai/codex/pull/17387/changes
Reference JWT/env stack: https://github.com/openai/codex/pull/18176

## Stack

1. https://github.com/openai/codex/pull/18757: full revert
2. https://github.com/openai/codex/pull/18871: isolated Agent Identity
crate
3. https://github.com/openai/codex/pull/18785: explicit AgentIdentity
auth mode and startup task allocation
4. https://github.com/openai/codex/pull/18811: migrate Codex backend
auth callsites through AuthProvider
5. This PR: accept AgentIdentity JWTs through login/env

## Testing

Tests: targeted login and Agent Identity crate tests, CLI checks, scoped
formatter/linter cleanup, and CI.

---------

Co-authored-by: Shijie Rao <shijie.rao@openai.com>
This commit is contained in:
efrazer-oai
2026-04-26 19:49:54 +00:00
committed by GitHub
co-authored by Shijie Rao
parent ac2bffa443
commit fed0a8f4fa
14 changed files with 587 additions and 49 deletions
+55 -9
View File
@@ -13,6 +13,7 @@ use codex_core::config::Config;
use codex_login::CLIENT_ID;
use codex_login::CodexAuth;
use codex_login::ServerOptions;
use codex_login::login_with_agent_identity;
use codex_login::login_with_api_key;
use codex_login::logout_with_revoke;
use codex_login::run_device_code_login;
@@ -34,6 +35,8 @@ const CHATGPT_LOGIN_DISABLED_MESSAGE: &str =
"ChatGPT login is disabled. Use API key login instead.";
const API_KEY_LOGIN_DISABLED_MESSAGE: &str =
"API key login is disabled. Use ChatGPT login instead.";
const AGENT_IDENTITY_LOGIN_DISABLED_MESSAGE: &str =
"Agent Identity login is disabled. Use API key login instead.";
const LOGIN_SUCCESS_MESSAGE: &str = "Successfully logged in";
/// Installs a small file-backed tracing layer for direct `codex login` flows.
@@ -187,31 +190,74 @@ pub async fn run_login_with_api_key(
}
}
pub async fn run_login_with_agent_identity(
cli_config_overrides: CliConfigOverrides,
agent_identity: String,
) -> ! {
let config = load_config_or_exit(cli_config_overrides).await;
let _login_log_guard = init_login_file_logging(&config);
tracing::info!("starting agent identity login flow");
if matches!(config.forced_login_method, Some(ForcedLoginMethod::Api)) {
eprintln!("{AGENT_IDENTITY_LOGIN_DISABLED_MESSAGE}");
std::process::exit(1);
}
match login_with_agent_identity(
&config.codex_home,
&agent_identity,
config.cli_auth_credentials_store_mode,
) {
Ok(_) => {
eprintln!("{LOGIN_SUCCESS_MESSAGE}");
std::process::exit(0);
}
Err(e) => {
eprintln!("Error logging in with Agent Identity: {e}");
std::process::exit(1);
}
}
}
pub fn read_api_key_from_stdin() -> String {
read_stdin_secret(
"--with-api-key expects the API key on stdin. Try piping it, e.g. `printenv OPENAI_API_KEY | codex login --with-api-key`.",
"Reading API key from stdin...",
"No API key provided via stdin.",
)
}
pub fn read_agent_identity_from_stdin() -> String {
read_stdin_secret(
"--with-agent-identity expects the Agent Identity token on stdin. Try piping it, e.g. `printenv CODEX_AGENT_IDENTITY | codex login --with-agent-identity`.",
"Reading Agent Identity token from stdin...",
"No Agent Identity token provided via stdin.",
)
}
fn read_stdin_secret(terminal_message: &str, reading_message: &str, empty_message: &str) -> String {
let mut stdin = std::io::stdin();
if stdin.is_terminal() {
eprintln!(
"--with-api-key expects the API key on stdin. Try piping it, e.g. `printenv OPENAI_API_KEY | codex login --with-api-key`."
);
eprintln!("{terminal_message}");
std::process::exit(1);
}
eprintln!("Reading API key from stdin...");
eprintln!("{reading_message}");
let mut buffer = String::new();
if let Err(err) = stdin.read_to_string(&mut buffer) {
eprintln!("Failed to read API key from stdin: {err}");
eprintln!("Failed to read stdin: {err}");
std::process::exit(1);
}
let api_key = buffer.trim().to_string();
if api_key.is_empty() {
eprintln!("No API key provided via stdin.");
let secret = buffer.trim().to_string();
if secret.is_empty() {
eprintln!("{empty_message}");
std::process::exit(1);
}
api_key
secret
}
/// Login using the OAuth device code flow.