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-oaiandShijie Rao authored and GitHub committed 2026-04-26 19:49:54 +00:00
1 parent ac2bffa443
commit fed0a8f4fa
14 files changed
+587 -49

No files matched your search

+39 -5
View File
@@ -207,12 +207,12 @@ impl CodexAuth {
return Ok(Self::from_api_key(api_key));
}
if auth_mode == ApiAuthMode::AgentIdentity {
let Some(record) = auth_dot_json.agent_identity else {
let Some(agent_identity) = auth_dot_json.agent_identity else {
return Err(std::io::Error::other(
"agent identity auth is missing an agent identity record.",
"agent identity auth is missing an agent identity token.",
));
};
return Ok(Self::AgentIdentity(AgentIdentityAuth::new(record)));
return Self::from_agent_identity_jwt(&agent_identity);
}
let storage_mode = auth_dot_json.storage_mode(auth_credentials_store_mode);
@@ -245,6 +245,11 @@ impl CodexAuth {
)
}
pub fn from_agent_identity_jwt(jwt: &str) -> std::io::Result<Self> {
let record = AgentIdentityAuthRecord::from_agent_identity_jwt(jwt)?;
Ok(Self::AgentIdentity(AgentIdentityAuth::new(record)))
}
pub fn auth_mode(&self) -> AuthMode {
match self {
Self::ApiKey(_) => AuthMode::ApiKey,
@@ -318,10 +323,10 @@ impl CodexAuth {
pub async fn initialize_runtime(
&self,
chatgpt_base_url: Option<String>,
_chatgpt_base_url: Option<String>,
) -> std::io::Result<()> {
match self {
Self::AgentIdentity(auth) => auth.ensure_runtime(chatgpt_base_url).await,
Self::AgentIdentity(auth) => auth.ensure_runtime().await,
Self::ApiKey(_) | Self::Chatgpt(_) | Self::ChatgptAuthTokens(_) => Ok(()),
}
}
@@ -474,6 +479,7 @@ impl ChatgptAuth {
pub const OPENAI_API_KEY_ENV_VAR: &str = "OPENAI_API_KEY";
pub const CODEX_API_KEY_ENV_VAR: &str = "CODEX_API_KEY";
pub const CODEX_AGENT_IDENTITY_ENV_VAR: &str = "CODEX_AGENT_IDENTITY";
pub fn read_openai_api_key_from_env() -> Option<String> {
env::var(OPENAI_API_KEY_ENV_VAR)
@@ -489,6 +495,13 @@ pub fn read_codex_api_key_from_env() -> Option<String> {
.filter(|value| !value.is_empty())
}
pub fn read_codex_agent_identity_from_env() -> Option<String> {
env::var(CODEX_AGENT_IDENTITY_ENV_VAR)
.ok()
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
}
/// Delete the auth.json file inside `codex_home` if it exists. Returns `Ok(true)`
/// if a file was removed, `Ok(false)` if no auth file was present.
pub fn logout(
@@ -529,6 +542,23 @@ pub fn login_with_api_key(
save_auth(codex_home, &auth_dot_json, auth_credentials_store_mode)
}
/// Writes an `auth.json` that contains only the Agent Identity token.
pub fn login_with_agent_identity(
codex_home: &Path,
agent_identity: &str,
auth_credentials_store_mode: AuthCredentialsStoreMode,
) -> std::io::Result<()> {
AgentIdentityAuthRecord::from_agent_identity_jwt(agent_identity)?;
let auth_dot_json = AuthDotJson {
auth_mode: Some(ApiAuthMode::AgentIdentity),
openai_api_key: None,
tokens: None,
last_refresh: None,
agent_identity: Some(agent_identity.to_string()),
};
save_auth(codex_home, &auth_dot_json, auth_credentials_store_mode)
}
/// Writes an in-memory auth payload for externally managed ChatGPT tokens.
pub fn login_with_chatgpt_auth_tokens(
codex_home: &Path,
@@ -714,6 +744,10 @@ fn load_auth(
return Ok(None);
}
if let Some(agent_identity) = read_codex_agent_identity_from_env() {
return CodexAuth::from_agent_identity_jwt(&agent_identity).map(Some);
}
// Fall back to the configured persistent store (file/keyring/auto) for managed auth.
let storage = create_auth_storage(codex_home.to_path_buf(), auth_credentials_store_mode);
let auth_dot_json = match storage.load()? {