refactor: make auth loading async (#19762)

## Summary

Auth loading used to expose synchronous construction helpers in several
places even though some auth sources now need async work. This PR makes
the auth-loading surface async and updates the callers to await it.

This is intentionally only plumbing. It does not change how
AgentIdentity tokens are decoded, how task runtime ids are allocated, or
how JWT signatures are verified.

## Stack

1. **This PR:** [refactor: make auth loading
async](https://github.com/openai/codex/pull/19762)
2. [refactor: load AgentIdentity runtime
eagerly](https://github.com/openai/codex/pull/19763)
3. [feat: verify AgentIdentity JWTs with
JWKS](https://github.com/openai/codex/pull/19764)

## Important call sites

| Area | Change |
| --- | --- |
| `codex-login` auth loading | `CodexAuth` and `AuthManager`
construction paths now await auth loading. |
| app-server startup | Auth manager construction is awaited during
initialization. |
| CLI/TUI/exec/MCP/chatgpt callers | Existing auth-loading calls now
await the same behavior. |
| cloud requirements storage loader | The loader becomes async so it can
share the same auth construction path. |
| auth tests | Tests that load auth now run in async contexts. |

## Testing

Tests: targeted Rust auth test compilation, formatter, scoped Clippy
fix, and Bazel lock check.
This commit is contained in:
efrazer-oai authored and GitHub committed 2026-04-27 11:00:27 -07:00
1 parent 4ed22fc7d2
commit 2009f6e894
25 files changed
+291 -203

No files matched your search

+35 -34
View File
@@ -46,7 +46,7 @@ async fn refresh_token_succeeds_updates_storage() -> Result<()> {
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let initial_last_refresh = Utc::now() - Duration::days(1);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
let initial_auth = AuthDotJson {
@@ -56,7 +56,7 @@ async fn refresh_token_succeeds_updates_storage() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
ctx.auth_manager
.refresh_token_from_authority()
@@ -110,7 +110,7 @@ async fn refresh_token_refreshes_when_auth_is_unchanged() -> Result<()> {
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let initial_last_refresh = Utc::now() - Duration::days(1);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
let initial_auth = AuthDotJson {
@@ -120,7 +120,7 @@ async fn refresh_token_refreshes_when_auth_is_unchanged() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
ctx.auth_manager
.refresh_token()
@@ -164,7 +164,7 @@ async fn refresh_token_skips_refresh_when_auth_changed() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = MockServer::start().await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let initial_last_refresh = Utc::now() - Duration::days(1);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
@@ -175,7 +175,7 @@ async fn refresh_token_skips_refresh_when_auth_changed() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let disk_tokens = build_tokens("disk-access-token", "disk-refresh-token");
let disk_auth = AuthDotJson {
@@ -230,7 +230,7 @@ async fn refresh_token_errors_on_account_mismatch() -> Result<()> {
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let initial_last_refresh = Utc::now() - Duration::days(1);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
let initial_auth = AuthDotJson {
@@ -240,7 +240,7 @@ async fn refresh_token_errors_on_account_mismatch() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let mut disk_tokens = build_tokens("disk-access-token", "disk-refresh-token");
disk_tokens.account_id = Some("other-account".to_string());
@@ -299,7 +299,7 @@ async fn returns_fresh_tokens_as_is() -> Result<()> {
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let stale_refresh = Utc::now() - Duration::days(9);
let fresh_access_token = access_token_with_expiration(Utc::now() + Duration::hours(1));
let initial_tokens = build_tokens(&fresh_access_token, INITIAL_REFRESH_TOKEN);
@@ -310,7 +310,7 @@ async fn returns_fresh_tokens_as_is() -> Result<()> {
last_refresh: Some(stale_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let cached_auth = ctx
.auth_manager
@@ -347,7 +347,7 @@ async fn refreshes_token_when_access_token_is_expired() -> Result<()> {
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let fresh_refresh = Utc::now() - Duration::days(1);
let expired_access_token = access_token_with_expiration(Utc::now() - Duration::hours(1));
let initial_tokens = build_tokens(&expired_access_token, INITIAL_REFRESH_TOKEN);
@@ -358,7 +358,7 @@ async fn refreshes_token_when_access_token_is_expired() -> Result<()> {
last_refresh: Some(fresh_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let cached_auth = ctx
.auth_manager
@@ -398,7 +398,7 @@ async fn auth_reloads_disk_auth_when_cached_auth_is_stale() -> Result<()> {
let server = MockServer::start().await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let stale_refresh = Utc::now() - Duration::days(9);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
let initial_auth = AuthDotJson {
@@ -408,7 +408,7 @@ async fn auth_reloads_disk_auth_when_cached_auth_is_stale() -> Result<()> {
last_refresh: Some(stale_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let fresh_refresh = Utc::now() - Duration::days(1);
let disk_tokens = build_tokens("disk-access-token", "disk-refresh-token");
@@ -461,7 +461,7 @@ async fn auth_reloads_disk_auth_without_calling_expired_refresh_token() -> Resul
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let stale_refresh = Utc::now() - Duration::days(9);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
let initial_auth = AuthDotJson {
@@ -471,7 +471,7 @@ async fn auth_reloads_disk_auth_without_calling_expired_refresh_token() -> Resul
last_refresh: Some(stale_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let fresh_refresh = Utc::now() - Duration::days(1);
let disk_tokens = build_tokens("disk-access-token", "disk-refresh-token");
@@ -522,7 +522,7 @@ async fn refresh_token_returns_permanent_error_for_expired_refresh_token() -> Re
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let initial_last_refresh = Utc::now() - Duration::days(1);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
let initial_auth = AuthDotJson {
@@ -532,7 +532,7 @@ async fn refresh_token_returns_permanent_error_for_expired_refresh_token() -> Re
last_refresh: Some(initial_last_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let err = ctx
.auth_manager
@@ -575,7 +575,7 @@ async fn refresh_token_does_not_retry_after_permanent_failure() -> Result<()> {
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let initial_last_refresh = Utc::now() - Duration::days(1);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
let initial_auth = AuthDotJson {
@@ -585,7 +585,7 @@ async fn refresh_token_does_not_retry_after_permanent_failure() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let first_err = ctx
.auth_manager
@@ -642,7 +642,7 @@ async fn refresh_token_reloads_changed_auth_after_permanent_failure() -> Result<
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let initial_last_refresh = Utc::now() - Duration::days(1);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
let initial_auth = AuthDotJson {
@@ -652,7 +652,7 @@ async fn refresh_token_reloads_changed_auth_after_permanent_failure() -> Result<
last_refresh: Some(initial_last_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let first_err = ctx
.auth_manager
@@ -723,7 +723,7 @@ async fn refresh_token_returns_transient_error_on_server_failure() -> Result<()>
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let initial_last_refresh = Utc::now() - Duration::days(1);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
let initial_auth = AuthDotJson {
@@ -733,7 +733,7 @@ async fn refresh_token_returns_transient_error_on_server_failure() -> Result<()>
last_refresh: Some(initial_last_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let err = ctx
.auth_manager
@@ -776,7 +776,7 @@ async fn unauthorized_recovery_reloads_then_refreshes_tokens() -> Result<()> {
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let initial_last_refresh = Utc::now() - Duration::days(1);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
let initial_auth = AuthDotJson {
@@ -786,7 +786,7 @@ async fn unauthorized_recovery_reloads_then_refreshes_tokens() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let disk_tokens = build_tokens("disk-access-token", "disk-refresh-token");
let disk_auth = AuthDotJson {
@@ -870,7 +870,7 @@ async fn unauthorized_recovery_errors_on_account_mismatch() -> Result<()> {
.mount(&server)
.await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let initial_last_refresh = Utc::now() - Duration::days(1);
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
let initial_auth = AuthDotJson {
@@ -880,7 +880,7 @@ async fn unauthorized_recovery_errors_on_account_mismatch() -> Result<()> {
last_refresh: Some(initial_last_refresh),
agent_identity: None,
};
ctx.write_auth(&initial_auth)?;
ctx.write_auth(&initial_auth).await?;
let mut disk_tokens = build_tokens("disk-access-token", "disk-refresh-token");
disk_tokens.account_id = Some("other-account".to_string());
@@ -941,7 +941,7 @@ async fn unauthorized_recovery_requires_chatgpt_auth() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = MockServer::start().await;
let ctx = RefreshTokenTestContext::new(&server)?;
let ctx = RefreshTokenTestContext::new(&server).await?;
let auth = AuthDotJson {
auth_mode: Some(AuthMode::ApiKey),
openai_api_key: Some("sk-test".to_string()),
@@ -949,7 +949,7 @@ async fn unauthorized_recovery_requires_chatgpt_auth() -> Result<()> {
last_refresh: None,
agent_identity: None,
};
ctx.write_auth(&auth)?;
ctx.write_auth(&auth).await?;
let mut recovery = ctx.auth_manager.unauthorized_recovery();
assert!(!recovery.has_next());
@@ -974,7 +974,7 @@ struct RefreshTokenTestContext {
}
impl RefreshTokenTestContext {
fn new(server: &MockServer) -> Result<Self> {
async fn new(server: &MockServer) -> Result<Self> {
let codex_home = TempDir::new()?;
let endpoint = format!("{}/oauth/token", server.uri());
@@ -985,7 +985,8 @@ impl RefreshTokenTestContext {
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
);
)
.await;
Ok(Self {
codex_home,
@@ -1000,13 +1001,13 @@ impl RefreshTokenTestContext {
.context("auth.json should exist")
}
fn write_auth(&self, auth_dot_json: &AuthDotJson) -> Result<()> {
async fn write_auth(&self, auth_dot_json: &AuthDotJson) -> Result<()> {
save_auth(
self.codex_home.path(),
auth_dot_json,
AuthCredentialsStoreMode::File,
)?;
self.auth_manager.reload();
self.auth_manager.reload().await;
Ok(())
}
}
+2 -1
View File
@@ -143,7 +143,8 @@ async fn auth_manager_logout_with_revoke_uses_cached_auth() -> Result<()> {
/*enable_codex_api_key_env*/ false,
AuthCredentialsStoreMode::File,
/*chatgpt_base_url*/ None,
);
)
.await;
save_auth(
codex_home.path(),
&chatgpt_auth_with_refresh_token("newer-disk-refresh-token"),