This document defines how codelia stores and uses provider credentials.
Implemented:
- Runtime provider auth resolution for
openai/anthropic/openrouter/moonshot/zai - Local
auth.jsonread/write (0600where possible) - OpenAI OAuth via loopback callback (
http://localhost:<port>/auth/callback) - OpenAI device-code login for SSH/headless environments
- SSH-aware manual browser fallback for loopback OAuth (
CODELIA_OAUTH_BROWSER=manual, auto-enabled when SSH env is detected, supports pasted callback URL/code) - UI-driven auth prompts (
ui.pick.request/ui.prompt.request/ui.confirm.request)
Planned:
- Startup-triggered first-run onboarding for no-auth users (TUI)
- Friendly onboarding copy for first-run auth and model selection steps
- Additional providers such as Gemini
- Production OAuth callback flow with external callback + DB-managed
oauth_state
- Support per-provider credentials (API key or OAuth) without relying on env vars.
- Store credentials in a local file (
auth.json) with minimal surface area. - Allow TUI to prompt for missing credentials (CLI later).
- Keep provider-specific auth logic isolated.
Use the storage paths defined in dev-docs/specs/storage-layout.md:
- Default:
~/.codelia/auth.json - XDG layout (opt-in):
$XDG_CONFIG_HOME/codelia/auth.json
Security expectations:
- Create the file with permissions
0600when possible. - Never print secrets in logs or UI events.
- Future: allow keychain/secret-store replacement.
{
"version": 1,
"providers": {
"openai": {
"method": "oauth",
"oauth": {
"access_token": "...",
"refresh_token": "...",
"expires_at": 1730000000000,
"account_id": "..."
}
},
"anthropic": {
"method": "api_key",
"api_key": "sk-ant-..."
}
}
}Notes:
expires_atis epoch milliseconds.account_idis optional (used for OpenAI subscription headers).- Env vars remain a fallback but should not overwrite stored auth unless explicitly requested.
- OpenAI
method: oauth(ChatGPT Plus/Pro subscription)method: api_key(standard OpenAI API key)
- Anthropic
method: api_keyonly
- OpenRouter
method: api_keyonly
- Moonshot
method: api_keyonly
- Z.ai
method: api_keyonly
Use an OAuth 2.0 Authorization Code flow with PKCE.
Profile-specific callback strategy:
dev-local(Implemented):- Loopback callback server is allowed (for local development ergonomics).
prod(Planned):- Public callback URL is required.
- OAuth
stateand PKCE verifier must be stored in DB (oauth_state) with TTL. - Callback handler must validate and one-time consume
statefrom DB.
High-level flow:
- Generate PKCE verifier/challenge and create
state. - Persist OAuth state metadata (
state, verifier, expires_at, provider, redirect_uri). - Open browser to the OpenAI authorization URL with PKCE params.
- Receive
codeat callback (loopback in dev-local, public endpoint in prod). - Validate
stateand one-time consume it from storage. - Exchange
codefor access + refresh tokens. - Persist tokens in auth storage.
- Refresh tokens when expired.
Notes:
- OpenAI device-code login is implemented through the Codex-style
/api/accounts/deviceauth/*flow. - Over SSH, manual mode can complete by pasting the redirected callback URL (or
code=...&state=...) back into the TUI.
Runtime usage:
- Use
access_tokenasAuthorization: Bearer <token>. - If available, send
ChatGPT-Account-Id: <account_id>. - Requests may need a different OpenAI endpoint for subscription usage (implementation detail, provider-specific).
Start onboarding immediately on TUI startup when all of the following are true:
auth.jsonhas no provider credentials for supported providers.- No supported provider API key env vars are available (
OPENAI_API_KEY,ANTHROPIC_API_KEY,OPENROUTER_API_KEY,MOONSHOT_API_KEY,ZAI_API_KEY).
If at least one provider credential exists (stored or env), runtime may skip onboarding and proceed with normal run flow.
Onboarding text should be short, clear, and friendly (no jargon-heavy copy).
- Welcome copy should explain what happens next.
- Provider options should include a one-line detail about auth method and recommended usage.
- Auth method copy should explain OAuth vs API key in plain language.
- Errors should be actionable and calm (for example, suggest retry or selecting a different method).
Example tone:
- "Let's set up your AI provider to get started."
- "You can switch this later from settings/commands."
When auth is missing for the selected provider:
- Prompt for provider (
ui.pick.requestif supported).- Include per-provider
detailtext in pick items.
- Include per-provider
- Prompt for auth method (OAuth or API key) with provider-specific options.
- If API key: prompt for input (
ui.prompt.request, masked in TUI if possible). - If OAuth:
- Implemented: OpenAI device-code flow for SSH/headless usage.
- Implemented: dev-local loopback callback server + browser auth URL.
- Implemented: SSH sessions default to manual browser instructions instead of attempting remote browser launch, and can finish by pasting the redirected callback URL/code into the UI.
- Planned: prod flow with public callback + DB-managed oauth state.
- Persist on success; continue to model selection step in onboarding.
- Show failure reason on error.
After successful auth in first-run onboarding:
- Request model candidates with
model.listfor the selected provider. - Show model picker immediately.
- Persist selected model with
model.set.
If UI does not support prompts/picks, runtime should return a clear error.
- Missing/invalid credentials: return a provider auth error and stop the run.
- OAuth timeout/cancel: user-facing error; do not reuse partial tokens.
- Token refresh failure: clear stored tokens and restart auth flow.
- CLI auth flow (mirror TUI prompts).
- Keychain/secret-store backend.
- Gemini support (per roadmap).