Skip to content

auth: let AuthManager own external bearer auth#16275

Closed
bolinfest wants to merge 1 commit intopr16277from
pr16275
Closed

auth: let AuthManager own external bearer auth#16275
bolinfest wants to merge 1 commit intopr16277from
pr16275

Conversation

@bolinfest
Copy link
Copy Markdown
Collaborator

@bolinfest bolinfest commented Mar 30, 2026

Summary

AuthManager already owns persisted auth state and UnauthorizedRecovery already powers multi-step 401 recovery. The missing piece for dynamic provider tokens was scope: we needed a way for request paths to use an external bearer source without inventing a second auth abstraction in codex-core or polluting the one global manager used by unrelated code.

This PR refactors AuthManager so a derived manager instance can carry an external bearer refresher while sharing the underlying stored auth snapshot, forced workspace override, and refresh lock. That keeps the design simple: all request tokens still come from AuthManager.auth(), and all 401 recovery still goes through UnauthorizedRecovery.

Motivation

This is groundwork for #15189.

That issue needs custom model providers to refresh short-lived bearer tokens dynamically, but the intended design is still:

  • AuthManager encapsulates token storage and refresh
  • UnauthorizedRecovery powers staged 401 recovery
  • all request tokens go through AuthManager.auth()

By making external bearer auth instance-scoped on AuthManager, the follow-on provider-auth PR can attach a command-backed token source to the request-facing managers used by normal API requests and /models, while leaving the base session AuthManager available for unrelated OpenAI auth use.

What changed

  • move external refresher ownership out of the cached auth snapshot and onto the AuthManager instance itself
  • add an instance-local external auth handle that distinguishes external ChatGPT refresh from external bearer refresh
  • add AuthManager::with_external_bearer_refresher(...) so callers can derive a provider-scoped manager that shares persisted auth state and refresh coordination with the base manager
  • teach AuthManager.auth() to resolve bearer-only external auth through CodexAuth::ApiKey
  • keep external ChatGPT refresh behavior unchanged while letting external bearer refresh skip all auth.json persistence
  • extend UnauthorizedRecovery so the external path also works for bearer-only refreshers

Testing

  • CARGO_TARGET_DIR=/tmp/codex-login-bearer-auth cargo test -p codex-login external_auth_tokens_without_chatgpt_metadata_cannot_seed_chatgpt_auth
  • CARGO_TARGET_DIR=/tmp/codex-login-auth-manager cargo test -p codex-login auth_manager_with_external_bearer_refresher_returns_provider_token_only_for_derived_manager
  • CARGO_TARGET_DIR=/tmp/codex-login-auth-manager cargo test -p codex-login unauthorized_recovery_uses_external_refresh_for_bearer_manager
  • CARGO_TARGET_DIR=/tmp/codex-login-auth-manager cargo check -p codex-app-server

Stack created with Sapling. Best reviewed with ReviewStack.

@bolinfest bolinfest changed the base branch from main to pr16277 March 30, 2026 23:51
@bolinfest bolinfest deleted the branch pr16277 March 30, 2026 23:52
@bolinfest bolinfest closed this Mar 30, 2026
@bolinfest bolinfest changed the title core: unify request auth and 401 recovery plumbing Mar 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant