C.W.K.
Stream
Lesson 05 of 07 · published

OAuth Authentication

~22 min · oauth, bearer-tokens, non-openai

Level 0Tokenizer
0 XP0/54 lessons0/10 achievements
0/120 XP to next level120 XP to go0% complete

The Codex CLI supports authenticating with a ChatGPT Pro/Teams account via OAuth 2.0 with PKCE. Tokens are stored in ~/.codex/auth.json.

Token Loading & Refresh

The 8-day stale threshold comes from the Codex CLI Rust source (codex-rs/core/src/auth.rs). Tokens older than 8 days trigger an automatic refresh.

Where OAuth shows up in this world

openai.com itself uses long-lived API keys, not OAuth. OAuth bearer tokens appear when you target hosts that front the OpenAI-compatible wire shape behind a corporate identity layer: ChatGPT Pro endpoints (cwkPippa's Codex vessel uses these), Google's Cloud Code Assist (cwkPippa's Gemini vessel), Azure OpenAI behind Entra ID, or any enterprise gateway that requires user-context auth.

The pattern: store a refresh token, exchange it for a short-lived bearer (5-60 min), put the bearer in Authorization: Bearer ..., refresh on 401. cwkPippa's adapter wraps this in a try-once-then-refresh-and-retry-once helper so the rest of the code doesn't think about token lifecycles.

Code

OAuth bearer token in the Authorization header·json
{
  "auth_mode": "chatgpt",
  "last_refresh": "2025-08-01T12:00:00Z",
  "tokens": {
    "access_token": "eyJ...",
    "id_token": "eyJ...",
    "refresh_token": "eyJ..."
  }
}
Refreshing a token on 401·python
import json
from pathlib import Path
from datetime import datetime, timezone, timedelta

CODEX_HOME = Path.home() / ".codex"
AUTH_FILE = CODEX_HOME / "auth.json"
STALE_THRESHOLD_DAYS = 8

def load_auth() -> dict:
    with open(AUTH_FILE) as f:
        return json.load(f)

def is_stale(auth: dict) -> bool:
    last_refresh_str = auth.get("last_refresh")
    if not last_refresh_str:
        return True
    last_refresh = datetime.fromisoformat(last_refresh_str.replace("Z", "+00:00"))
    return datetime.now(timezone.utc) - last_refresh > timedelta(days=STALE_THRESHOLD_DAYS)

def get_bearer_token() -> str:
    auth = load_auth()
    if auth.get("auth_mode") != "chatgpt":
        raise ValueError("Expected ChatGPT auth mode")
    if is_stale(auth):
        auth = refresh_tokens(auth)  # refresh via OAuth endpoint
    return auth["tokens"]["access_token"]

External links

Exercise

Mock an OAuth-protected provider (200 with the right token, 401 otherwise). Wrap it in a Python adapter that auto-refreshes on 401, then retries the original call exactly once.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.