refactor: route Codex auth through AuthProvider (#18811)

## Summary

This PR moves Codex backend request authentication from direct
bearer-token handling to `AuthProvider`.

The new `codex-auth-provider` crate defines the shared request-auth
trait. `CodexAuth::provider()` returns a provider that can apply all
headers needed for the selected auth mode.

This lets ChatGPT token auth and AgentIdentity auth share the same
callsite path:
- ChatGPT token auth applies bearer auth plus account/FedRAMP headers
where needed.
- AgentIdentity auth applies AgentAssertion plus account/FedRAMP headers
where needed.

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

## Callsite Migration

| Area | Change |
| --- | --- |
| backend-client | accepts an `AuthProvider` instead of a raw
token/header |
| chatgpt client/connectors | applies auth through
`CodexAuth::provider()` |
| cloud tasks | keeps Codex-backend gating, applies auth through
provider |
| cloud requirements | uses Codex-backend auth checks and provider
headers |
| app-server remote control | applies provider headers for backend calls
|
| MCP Apps/connectors | gates on `uses_codex_backend()` and keys caches
from generic account getters |
| model refresh | treats AgentIdentity as Codex-backend auth |
| OpenAI file upload path | rejects non-Codex-backend auth before
applying headers |
| core client setup | keeps model-provider auth flow and allows
AgentIdentity through provider-backed OpenAI auth |

## 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. This PR: migrate Codex backend auth callsites through AuthProvider
5. https://github.com/openai/codex/pull/18904: accept AgentIdentity JWTs
and load `CODEX_AGENT_IDENTITY`

## Testing

Tests: targeted Rust checks, cargo-shear, Bazel lock check, and CI.
This commit is contained in:
efrazer-oai
2026-04-23 17:14:02 -07:00
committed by GitHub
parent a9f75e5cda
commit 5882f3f95e
55 changed files with 551 additions and 490 deletions
+3 -10
View File
@@ -608,7 +608,7 @@ fn ensure_chatgpt_auth(auth: Option<&CodexAuth>) -> Result<&CodexAuth, RemotePlu
let Some(auth) = auth else {
return Err(RemotePluginCatalogError::AuthRequired);
};
if !auth.is_chatgpt_auth() {
if !auth.uses_codex_backend() {
return Err(RemotePluginCatalogError::UnsupportedAuthMode);
}
Ok(auth)
@@ -618,16 +618,9 @@ fn authenticated_request(
request: RequestBuilder,
auth: &CodexAuth,
) -> Result<RequestBuilder, RemotePluginCatalogError> {
let token = auth
.get_token()
.map_err(RemotePluginCatalogError::AuthToken)?;
let mut request = request
Ok(request
.timeout(REMOTE_PLUGIN_CATALOG_TIMEOUT)
.bearer_auth(token);
if let Some(account_id) = auth.get_account_id() {
request = request.header("chatgpt-account-id", account_id);
}
Ok(request)
.headers(codex_model_provider::auth_provider_from_auth(auth).to_auth_headers()))
}
async fn send_and_decode<T: for<'de> Deserialize<'de>>(
+13 -28
View File
@@ -123,23 +123,17 @@ pub async fn fetch_remote_plugin_status(
let Some(auth) = auth else {
return Err(RemotePluginFetchError::AuthRequired);
};
if !auth.is_chatgpt_auth() {
if !auth.uses_codex_backend() {
return Err(RemotePluginFetchError::UnsupportedAuthMode);
}
let base_url = config.chatgpt_base_url.trim_end_matches('/');
let url = format!("{base_url}/plugins/list");
let client = build_reqwest_client();
let token = auth
.get_token()
.map_err(RemotePluginFetchError::AuthToken)?;
let mut request = client
let request = client
.get(&url)
.timeout(REMOTE_PLUGIN_FETCH_TIMEOUT)
.bearer_auth(token);
if let Some(account_id) = auth.get_account_id() {
request = request.header("chatgpt-account-id", account_id);
}
.headers(codex_model_provider::auth_provider_from_auth(auth).to_auth_headers());
let response = request
.send()
@@ -176,14 +170,9 @@ pub async fn fetch_remote_featured_plugin_ids(
)])
.timeout(REMOTE_FEATURED_PLUGIN_FETCH_TIMEOUT);
if let Some(auth) = auth.filter(|auth| auth.is_chatgpt_auth()) {
let token = auth
.get_token()
.map_err(RemotePluginFetchError::AuthToken)?;
request = request.bearer_auth(token);
if let Some(account_id) = auth.get_account_id() {
request = request.header("chatgpt-account-id", account_id);
}
if let Some(auth) = auth.filter(|auth| auth.uses_codex_backend()) {
request =
request.headers(codex_model_provider::auth_provider_from_auth(auth).to_auth_headers());
}
let response = request
@@ -223,11 +212,13 @@ pub async fn uninstall_remote_plugin(
Ok(())
}
fn ensure_chatgpt_auth(auth: Option<&CodexAuth>) -> Result<&CodexAuth, RemotePluginMutationError> {
fn ensure_codex_backend_auth(
auth: Option<&CodexAuth>,
) -> Result<&CodexAuth, RemotePluginMutationError> {
let Some(auth) = auth else {
return Err(RemotePluginMutationError::AuthRequired);
};
if !auth.is_chatgpt_auth() {
if !auth.uses_codex_backend() {
return Err(RemotePluginMutationError::UnsupportedAuthMode);
}
Ok(auth)
@@ -243,19 +234,13 @@ async fn post_remote_plugin_mutation(
plugin_id: &str,
action: &str,
) -> Result<RemotePluginMutationResponse, RemotePluginMutationError> {
let auth = ensure_chatgpt_auth(auth)?;
let auth = ensure_codex_backend_auth(auth)?;
let url = remote_plugin_mutation_url(config, plugin_id, action)?;
let client = build_reqwest_client();
let token = auth
.get_token()
.map_err(RemotePluginMutationError::AuthToken)?;
let mut request = client
let request = client
.post(url.clone())
.timeout(REMOTE_PLUGIN_MUTATION_TIMEOUT)
.bearer_auth(token);
if let Some(account_id) = auth.get_account_id() {
request = request.header("chatgpt-account-id", account_id);
}
.headers(codex_model_provider::auth_provider_from_auth(auth).to_auth_headers());
let response = request
.send()