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
Recommended iOS Design
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:
- host app creates
ChatGPTAuthProvider(method: .deviceCode, ...) - host app provides
DeviceCodePromptCoordinatorfromCodexKitUI - runtime starts sign-in through
AgentRuntime.signIn() - device-code prompt state is surfaced to SwiftUI
- user completes ChatGPT sign-in in the verification flow
- runtime exchanges the authorization code for tokens
- runtime persists the resulting
ChatGPTSessionwithKeychainSessionSecureStore ChatGPTSessionManagerrefreshes later when needed
This matches Codex’s auth lifecycle while avoiding desktop-only redirect assumptions.
Why Device Code Is Recommended
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:
ChatGPTDeviceCodeAuthProvideris the default documented iOS sign-in pathChatGPTOAuthProviderremains 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:
KeychainSessionSecureStorekSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
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:
- app creates
AgentRuntimewith a configured auth provider and secure store - app calls
restore() ChatGPTSessionManagerloads any storedChatGPTSession- app reads current session state through
AgentRuntime.currentSession()
At request time:
- runtime requires a valid session
- runtime refreshes if the session is near expiry
- runtime uses the refreshed session for backend calls
At logout:
- runtime clears the in-memory session
- runtime deletes the Keychain session
- 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:
ChatGPTSessionChatGPTAuthProviderKeychainSessionSecureStoreChatGPTSessionManagerChatGPTDeviceCodeAuthProviderChatGPTOAuthProviderDeviceCodePromptCoordinatorinCodexKitUI
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