The "Sign in with ChatGPT (Codex)" OAuth flow (ADR 0002), implemented natively over
Finch. Supports browser PKCE (localhost callback on 127.0.0.1:1455) and the
device-code fallback for headless environments. Constants and request shapes mirror
the Pi reference implementation.
Browser flow:
generate_pkce/0+generate_state/0+build_authorize_url/2start_callback_server/1+wait_for_callback/2— capture the authorization code athttp://localhost:1455/auth/callbackexchange_for_credential/3— exchange the code at/oauth/token
Device-code flow:
start_device_auth/0— POST the user-code endpoint →{device_auth_id, user_code, interval}. The user entersuser_codeatverification_uri.poll_for_authorization/2— poll the device-token endpoint until the user approves; the server returns anauthorization_codeand the PKCEcode_verifierit generated.exchange_for_credential/3— exchange that code at/oauth/token
refresh/1 swaps a refresh token for a fresh credential. All functions return
structured errors (ADR 0005).
Summary
Functions
Extract chatgpt_account_id from an access token's JWT claims.
Registered redirect URI for the browser OAuth flow.
Build the browser authorize URL for the user to open.
Close the callback listen socket.
Exchange an authorization code (+ verifier) for a stored-shape credential.
Generate PKCE verifier/challenge pair for the browser flow.
Generate an OAuth state value for CSRF protection.
Poll until the user approves (or the flow times out). Honors slow_down by widening
the interval (RFC 8628). Returns {authorization_code, code_verifier}.
Refresh an expired/expiring subscription credential.
How long before expiry we proactively refresh (ms).
Start the localhost callback listener. Accepts :host, :port (testing).
Begin device authorization. Returns device info to display to the user.
Wait for one browser callback. Requires :state; accepts :timeout_ms.
Types
@type device() :: %{ device_auth_id: String.t(), user_code: String.t(), interval: pos_integer(), verification_uri: String.t(), expires_in: pos_integer() }
Functions
Extract chatgpt_account_id from an access token's JWT claims.
@spec browser_redirect_uri() :: String.t()
Registered redirect URI for the browser OAuth flow.
Build the browser authorize URL for the user to open.
@spec close_callback_server(port()) :: :ok
Close the callback listen socket.
@spec exchange_for_credential(String.t(), String.t(), keyword()) :: {:ok, credential()} | {:error, map()}
Exchange an authorization code (+ verifier) for a stored-shape credential.
Pass redirect_uri: for the browser flow; device-code uses the device callback URI by
default.
Generate PKCE verifier/challenge pair for the browser flow.
@spec generate_state() :: String.t()
Generate an OAuth state value for CSRF protection.
@spec poll_for_authorization( device(), keyword() ) :: {:ok, %{authorization_code: String.t(), code_verifier: String.t()}} | {:error, map()}
Poll until the user approves (or the flow times out). Honors slow_down by widening
the interval (RFC 8628). Returns {authorization_code, code_verifier}.
@spec refresh(String.t()) :: {:ok, credential()} | {:error, map()}
Refresh an expired/expiring subscription credential.
How long before expiry we proactively refresh (ms).
Start the localhost callback listener. Accepts :host, :port (testing).
Begin device authorization. Returns device info to display to the user.
Wait for one browser callback. Requires :state; accepts :timeout_ms.