Rename agent identity login surface to access token (#21059)

## Why
The external startup/login surface for this auth path should talk about
an access token instead of exposing the internal Agent Identity
terminology. Users should pass `CODEX_ACCESS_TOKEN` or pipe a token into
`codex login --with-access-token`; the old external env/flag spellings
are removed so there is only one supported user-facing path.

## What Changed
- Added `CODEX_ACCESS_TOKEN` as the supported environment variable for
this auth path.
- Added `codex login --with-access-token` as the supported stdin-based
login command.
- Removed the legacy `CODEX_AGENT_IDENTITY` env-var fallback and hidden
`--with-agent-identity` CLI alias.
- Updated CLI error, status, and stdin prompts to use access-token
language.
- Added coverage for access-token env loading, CLI login failure
behavior, and renamed login status text.

## Validation
- `cargo test -p codex-login`
- `cargo test -p codex-cli`
- `just fix -p codex-login`
- `just fix -p codex-cli`
This commit is contained in:
Shijie Rao authored and GitHub committed 2026-05-04 19:43:48 -07:00
1 parent d85783901c
commit 0d418f478d
7 files changed
+79 -75

No files matched your search

+32 -28
View File
@@ -84,7 +84,7 @@ fn login_with_api_key_overwrites_existing_auth_json() {
}
#[tokio::test]
async fn login_with_agent_identity_writes_only_token() {
async fn login_with_access_token_writes_only_token() {
let dir = tempdir().unwrap();
let auth_path = dir.path().join("auth.json");
let record = agent_identity_record("account-123");
@@ -99,14 +99,14 @@ async fn login_with_agent_identity_writes_only_token() {
.await;
let chatgpt_base_url = format!("{}/backend-api", server.uri());
super::login_with_agent_identity(
super::login_with_access_token(
dir.path(),
&agent_identity,
AuthCredentialsStoreMode::File,
Some(&chatgpt_base_url),
)
.await
.expect("login_with_agent_identity should succeed");
.expect("login_with_access_token should succeed");
let storage = FileAuthStorage::new(dir.path().to_path_buf());
let auth = storage
@@ -123,27 +123,27 @@ async fn login_with_agent_identity_writes_only_token() {
}
#[tokio::test]
async fn login_with_agent_identity_rejects_invalid_jwt() {
async fn login_with_access_token_rejects_invalid_jwt() {
let dir = tempdir().unwrap();
let err = super::login_with_agent_identity(
let err = super::login_with_access_token(
dir.path(),
"not-a-jwt",
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
)
.await
.expect_err("invalid Agent Identity token should fail");
.expect_err("invalid access token should fail");
assert_eq!(err.kind(), std::io::ErrorKind::Other);
assert!(
!get_auth_file(dir.path()).exists(),
"invalid Agent Identity token should not write auth.json"
"invalid access token should not write auth.json"
);
}
#[tokio::test]
async fn login_with_agent_identity_rejects_unsigned_jwt() {
async fn login_with_access_token_rejects_unsigned_jwt() {
let dir = tempdir().unwrap();
let record = agent_identity_record("account-123");
let agent_identity = fake_agent_identity_jwt(&record).expect("fake agent identity");
@@ -156,18 +156,18 @@ async fn login_with_agent_identity_rejects_unsigned_jwt() {
.await;
let chatgpt_base_url = format!("{}/backend-api", server.uri());
super::login_with_agent_identity(
super::login_with_access_token(
dir.path(),
&agent_identity,
AuthCredentialsStoreMode::File,
Some(&chatgpt_base_url),
)
.await
.expect_err("unsigned Agent Identity token should fail");
.expect_err("unsigned access token should fail");
assert!(
!get_auth_file(dir.path()).exists(),
"unsigned Agent Identity token should not write auth.json"
"unsigned access token should not write auth.json"
);
server.verify().await;
}
@@ -176,7 +176,7 @@ async fn login_with_agent_identity_rejects_unsigned_jwt() {
#[serial(codex_auth_env)]
async fn missing_auth_json_returns_none() {
let dir = tempdir().unwrap();
let _agent_guard = EnvVarGuard::remove(CODEX_AGENT_IDENTITY_ENV_VAR);
let _access_token_guard = remove_access_token_env_var();
let auth = CodexAuth::from_auth_storage(
dir.path(),
AuthCredentialsStoreMode::File,
@@ -191,7 +191,7 @@ async fn missing_auth_json_returns_none() {
#[serial(codex_auth_env)]
async fn pro_account_with_no_api_key_uses_chatgpt_auth() {
let codex_home = tempdir().unwrap();
let _agent_guard = EnvVarGuard::remove(CODEX_AGENT_IDENTITY_ENV_VAR);
let _access_token_guard = remove_access_token_env_var();
let fake_jwt = write_auth_file(
AuthFileParams {
openai_api_key: None,
@@ -250,7 +250,7 @@ async fn pro_account_with_no_api_key_uses_chatgpt_auth() {
#[serial(codex_auth_env)]
async fn loads_api_key_from_auth_json() {
let dir = tempdir().unwrap();
let _agent_guard = EnvVarGuard::remove(CODEX_AGENT_IDENTITY_ENV_VAR);
let _access_token_guard = remove_access_token_env_var();
let auth_file = dir.path().join("auth.json");
std::fs::write(
auth_file,
@@ -324,7 +324,7 @@ async fn unauthorized_recovery_reports_mode_and_step_names() {
#[serial(codex_auth_env)]
async fn refresh_failure_is_scoped_to_the_matching_auth_snapshot() {
let codex_home = tempdir().unwrap();
let _agent_guard = EnvVarGuard::remove(CODEX_AGENT_IDENTITY_ENV_VAR);
let _access_token_guard = remove_access_token_env_var();
write_auth_file(
AuthFileParams {
openai_api_key: None,
@@ -704,9 +704,13 @@ impl Drop for EnvVarGuard {
}
}
fn remove_access_token_env_var() -> EnvVarGuard {
EnvVarGuard::remove(CODEX_ACCESS_TOKEN_ENV_VAR)
}
#[tokio::test]
#[serial(codex_auth_env)]
async fn load_auth_reads_agent_identity_from_env() {
async fn load_auth_reads_access_token_from_env() {
let codex_home = tempdir().unwrap();
let expected_record = agent_identity_record("account-123");
let agent_identity =
@@ -727,7 +731,7 @@ async fn load_auth_reads_agent_identity_from_env() {
.expect(1)
.mount(&server)
.await;
let _agent_guard = EnvVarGuard::set(CODEX_AGENT_IDENTITY_ENV_VAR, &agent_identity);
let _access_token_guard = EnvVarGuard::set(CODEX_ACCESS_TOKEN_ENV_VAR, &agent_identity);
let chatgpt_base_url = format!("{}/backend-api", server.uri());
let _authapi_guard =
@@ -760,7 +764,7 @@ async fn load_auth_keeps_codex_api_key_env_precedence() {
let codex_home = tempdir().unwrap();
let record = agent_identity_record("account-123");
let agent_identity = fake_agent_identity_jwt(&record).expect("fake agent identity");
let _agent_guard = EnvVarGuard::set(CODEX_AGENT_IDENTITY_ENV_VAR, &agent_identity);
let _access_token_guard = EnvVarGuard::set(CODEX_ACCESS_TOKEN_ENV_VAR, &agent_identity);
let _api_key_guard = EnvVarGuard::set(CODEX_API_KEY_ENV_VAR, "sk-env");
let auth = super::load_auth(
@@ -780,7 +784,7 @@ async fn load_auth_keeps_codex_api_key_env_precedence() {
#[serial(codex_auth_env)]
async fn enforce_login_restrictions_logs_out_for_method_mismatch() {
let codex_home = tempdir().unwrap();
let _agent_guard = EnvVarGuard::remove(CODEX_AGENT_IDENTITY_ENV_VAR);
let _access_token_guard = remove_access_token_env_var();
login_with_api_key(codex_home.path(), "sk-test", AuthCredentialsStoreMode::File)
.expect("seed api key");
@@ -805,7 +809,7 @@ async fn enforce_login_restrictions_logs_out_for_method_mismatch() {
#[serial(codex_auth_env)]
async fn enforce_login_restrictions_logs_out_for_workspace_mismatch() {
let codex_home = tempdir().unwrap();
let _agent_guard = EnvVarGuard::remove(CODEX_AGENT_IDENTITY_ENV_VAR);
let _access_token_guard = remove_access_token_env_var();
let _jwt = write_auth_file(
AuthFileParams {
openai_api_key: None,
@@ -837,7 +841,7 @@ async fn enforce_login_restrictions_logs_out_for_workspace_mismatch() {
#[serial(codex_auth_env)]
async fn enforce_login_restrictions_allows_matching_workspace() {
let codex_home = tempdir().unwrap();
let _agent_guard = EnvVarGuard::remove(CODEX_AGENT_IDENTITY_ENV_VAR);
let _access_token_guard = remove_access_token_env_var();
let _jwt = write_auth_file(
AuthFileParams {
openai_api_key: None,
@@ -869,7 +873,7 @@ async fn enforce_login_restrictions_allows_matching_workspace() {
async fn enforce_login_restrictions_allows_api_key_if_login_method_not_set_but_forced_chatgpt_workspace_id_is_set()
{
let codex_home = tempdir().unwrap();
let _agent_guard = EnvVarGuard::remove(CODEX_AGENT_IDENTITY_ENV_VAR);
let _access_token_guard = remove_access_token_env_var();
login_with_api_key(codex_home.path(), "sk-test", AuthCredentialsStoreMode::File)
.expect("seed api key");
@@ -893,7 +897,7 @@ async fn enforce_login_restrictions_allows_api_key_if_login_method_not_set_but_f
#[serial(codex_auth_env)]
async fn enforce_login_restrictions_blocks_env_api_key_when_chatgpt_required() {
let _guard = EnvVarGuard::set(CODEX_API_KEY_ENV_VAR, "sk-env");
let _agent_guard = EnvVarGuard::remove(CODEX_AGENT_IDENTITY_ENV_VAR);
let _access_token_guard = remove_access_token_env_var();
let codex_home = tempdir().unwrap();
let config = build_config(
@@ -1069,7 +1073,7 @@ async fn assert_agent_identity_plan_alias(
#[serial(codex_auth_env)]
async fn plan_type_maps_known_plan() {
let codex_home = tempdir().unwrap();
let _agent_guard = EnvVarGuard::remove(CODEX_AGENT_IDENTITY_ENV_VAR);
let _access_token_guard = remove_access_token_env_var();
let _jwt = write_auth_file(
AuthFileParams {
openai_api_key: None,
@@ -1097,7 +1101,7 @@ async fn plan_type_maps_known_plan() {
#[serial(codex_auth_env)]
async fn plan_type_maps_self_serve_business_usage_based_plan() {
let codex_home = tempdir().unwrap();
let _agent_guard = EnvVarGuard::remove(CODEX_AGENT_IDENTITY_ENV_VAR);
let _access_token_guard = remove_access_token_env_var();
let _jwt = write_auth_file(
AuthFileParams {
openai_api_key: None,
@@ -1128,7 +1132,7 @@ async fn plan_type_maps_self_serve_business_usage_based_plan() {
#[serial(codex_auth_env)]
async fn plan_type_maps_enterprise_cbp_usage_based_plan() {
let codex_home = tempdir().unwrap();
let _agent_guard = EnvVarGuard::remove(CODEX_AGENT_IDENTITY_ENV_VAR);
let _access_token_guard = remove_access_token_env_var();
let _jwt = write_auth_file(
AuthFileParams {
openai_api_key: None,
@@ -1159,7 +1163,7 @@ async fn plan_type_maps_enterprise_cbp_usage_based_plan() {
#[serial(codex_auth_env)]
async fn plan_type_maps_unknown_to_unknown() {
let codex_home = tempdir().unwrap();
let _agent_guard = EnvVarGuard::remove(CODEX_AGENT_IDENTITY_ENV_VAR);
let _access_token_guard = remove_access_token_env_var();
let _jwt = write_auth_file(
AuthFileParams {
openai_api_key: None,
@@ -1187,7 +1191,7 @@ async fn plan_type_maps_unknown_to_unknown() {
#[serial(codex_auth_env)]
async fn missing_plan_type_maps_to_unknown() {
let codex_home = tempdir().unwrap();
let _agent_guard = EnvVarGuard::remove(CODEX_AGENT_IDENTITY_ENV_VAR);
let _access_token_guard = remove_access_token_env_var();
let _jwt = write_auth_file(
AuthFileParams {
openai_api_key: None,
+14 -13
View File
@@ -464,7 +464,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 const CODEX_ACCESS_TOKEN_ENV_VAR: &str = "CODEX_ACCESS_TOKEN";
pub fn read_openai_api_key_from_env() -> Option<String> {
env::var(OPENAI_API_KEY_ENV_VAR)
@@ -474,14 +474,15 @@ pub fn read_openai_api_key_from_env() -> Option<String> {
}
pub fn read_codex_api_key_from_env() -> Option<String> {
env::var(CODEX_API_KEY_ENV_VAR)
.ok()
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
read_non_empty_env_var(CODEX_API_KEY_ENV_VAR)
}
pub fn read_codex_agent_identity_from_env() -> Option<String> {
env::var(CODEX_AGENT_IDENTITY_ENV_VAR)
pub fn read_codex_access_token_from_env() -> Option<String> {
read_non_empty_env_var(CODEX_ACCESS_TOKEN_ENV_VAR)
}
fn read_non_empty_env_var(key: &str) -> Option<String> {
env::var(key)
.ok()
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty())
@@ -540,10 +541,10 @@ 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 async fn login_with_agent_identity(
/// Writes an `auth.json` that contains only the access token.
pub async fn login_with_access_token(
codex_home: &Path,
agent_identity: &str,
access_token: &str,
auth_credentials_store_mode: AuthCredentialsStoreMode,
chatgpt_base_url: Option<&str>,
) -> std::io::Result<()> {
@@ -551,13 +552,13 @@ pub async fn login_with_agent_identity(
.unwrap_or(DEFAULT_CHATGPT_BACKEND_BASE_URL)
.trim_end_matches('/')
.to_string();
verified_agent_identity_record(agent_identity, &base_url).await?;
verified_agent_identity_record(access_token, &base_url).await?;
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()),
agent_identity: Some(access_token.to_string()),
};
save_auth(codex_home, &auth_dot_json, auth_credentials_store_mode)
}
@@ -753,7 +754,7 @@ async fn load_auth(
return Ok(None);
}
if let Some(agent_identity) = read_codex_agent_identity_from_env() {
if let Some(agent_identity) = read_codex_access_token_from_env() {
return CodexAuth::from_agent_identity_jwt(&agent_identity, chatgpt_base_url)
.await
.map(Some);
+3 -3
View File
@@ -22,7 +22,7 @@ pub use auth::AuthDotJson;
pub use auth::AuthManager;
pub use auth::AuthManagerConfig;
pub use auth::CLIENT_ID;
pub use auth::CODEX_AGENT_IDENTITY_ENV_VAR;
pub use auth::CODEX_ACCESS_TOKEN_ENV_VAR;
pub use auth::CODEX_API_KEY_ENV_VAR;
pub use auth::CodexAuth;
pub use auth::ExternalAuth;
@@ -38,11 +38,11 @@ pub use auth::UnauthorizedRecovery;
pub use auth::default_client;
pub use auth::enforce_login_restrictions;
pub use auth::load_auth_dot_json;
pub use auth::login_with_agent_identity;
pub use auth::login_with_access_token;
pub use auth::login_with_api_key;
pub use auth::logout;
pub use auth::logout_with_revoke;
pub use auth::read_codex_agent_identity_from_env;
pub use auth::read_codex_access_token_from_env;
pub use auth::read_openai_api_key_from_env;
pub use auth::save_auth;
pub use auth_env_telemetry::AuthEnvTelemetry;