ChatGPT Auth on iOS

September 9, 2026 · View on GitHub

Documentation index · CodexKit

Upstream Codex Model

Codex separates:

  • ChatGPT auth acquisition
  • token persistence
  • token refresh
  • externally managed token refresh callbacks

Those concepts port cleanly to iOS. The desktop transport does not.

The important upstream ideas we keep are:

  • a durable ChatGPT session model
  • refresh-on-demand behavior
  • externally supplied session handoff
  • account metadata extraction from auth tokens

The iOS host app owns:

  • when sign-in starts
  • what sign-in UI is shown
  • Keychain service and account naming
  • approval and prompt presentation

The runtime owns:

  • session lifecycle
  • refresh coordination
  • account/session restoration
  • normalization into ChatGPTSession

Default Live Path

The recommended live iOS path in this repo is now:

  1. host app creates ChatGPTAuthProvider(method: .deviceCode, ...)
  2. host app provides DeviceCodePromptCoordinator from CodexKitUI
  3. runtime starts sign-in through AgentRuntime.signIn()
  4. device-code prompt state is surfaced to SwiftUI
  5. user completes ChatGPT sign-in in the verification flow
  6. runtime exchanges the authorization code for tokens
  7. runtime persists the resulting ChatGPTSession with KeychainSessionSecureStore
  8. ChatGPTSessionManager refreshes later when needed

This matches Codex’s auth lifecycle while avoiding desktop-only redirect assumptions.

The redirect-based browser OAuth path from Codex is useful, but on iOS it depends on upstream redirect acceptance and presentation details that are more fragile than the device-code flow.

For that reason:

  • ChatGPTDeviceCodeAuthProvider is the default documented iOS sign-in path
  • ChatGPTOAuthProvider remains available for advanced integrations

Advanced Browser OAuth Path

When an app specifically wants a browser callback flow, it can still use:

  • ChatGPTAuthProvider(method: .oauth)
  • the built-in localhost callback flow

That path preserves Codex’s PKCE and token exchange model, but it is not the default recommendation for first-time integration. ChatGPTOAuthProvider remains the underlying public OAuth implementation, while AgentRuntime.Configuration accepts the unified ChatGPTAuthProvider wrapper.

Account Name

Both sign-in methods populate session.account.name from the ID token's name claim during sign-in and refresh. The name is optional: an absent or null claim produces nil, and existing saved sessions without a name continue to load. Name availability depends on the token returned by the authentication service.

Use session.account.displayName for UI labels. It trims surrounding whitespace from the name and falls back to the account email when the name is absent or blank. name retains the original value.

if let session = await runtime.currentSession() {
    print("Signed in as \(session.account.displayName)")
}

Apps supplying their own sessions can pass name: to ChatGPTAccount(id:email:plan:name:). The original ChatGPTAccount(id:email:plan:) initializer remains available, including as a function reference, and sets the name to nil.

Secure Storage

The default storage is:

  • KeychainSessionSecureStore
  • kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly

Why:

  • survives relaunch
  • stays inside iOS sandbox constraints
  • avoids plaintext token files

AgentRuntime.Configuration uses this Keychain-backed store directly. Apps can choose distinct service and account identifiers when they need separate session domains.

Session Lifecycle

At launch:

  1. app creates AgentRuntime with a configured auth provider and secure store
  2. app calls restore()
  3. ChatGPTSessionManager loads any stored ChatGPTSession
  4. app reads current session state through AgentRuntime.currentSession()

At request time:

  1. runtime requires a valid session
  2. runtime refreshes if the session is near expiry
  3. runtime uses the refreshed session for backend calls

At logout:

  1. runtime clears the in-memory session
  2. runtime deletes the Keychain session
  3. UI updates immediately

Error Model

The auth layer should surface user-meaningful failures such as:

  • sign-in cancelled
  • missing session
  • callback mismatch
  • token exchange failed
  • refresh failed
  • auth edge challenge / rate-limit failure

These are normalized through AgentRuntimeError rather than leaking raw transport details into app UI.

First-Class Types In This Repo

The current auth surface is:

  • ChatGPTSession
  • ChatGPTAuthProvider
  • KeychainSessionSecureStore
  • ChatGPTSessionManager
  • ChatGPTDeviceCodeAuthProvider
  • ChatGPTOAuthProvider
  • DeviceCodePromptCoordinator in CodexKitUI

Bottom Line

Port the Codex auth model, not Codex’s desktop login transport.

The intended mapping is:

  • Codex AuthManager -> ChatGPTSessionManager
  • externally supplied session handoff -> AgentRuntime.useSession(_:)
  • Codex device-code login model -> ChatGPTDeviceCodeAuthProvider
  • Codex file/keyring persistence -> KeychainSessionSecureStore
  • app-facing prompt state -> DeviceCodePromptCoordinator