> ## Documentation Index
> Fetch the complete documentation index at: https://chatcli.edilsonfreitas.com/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth Authentication

> Authenticate with AI providers via OAuth, without needing to manage API keys manually.

ChatCLI supports authentication via **OAuth 2.0 with PKCE** and **Device Flow (RFC 8628)** for providers that offer these options. This allows you to use your existing plan (such as **ChatGPT Plus**, **Codex**, **Claude Pro**, or **GitHub Copilot**) directly in the terminal, without needing to generate or paste API keys.

***

## Authentication Methods

| Method                        | Description                                             | When to Use                                             |
| ----------------------------- | ------------------------------------------------------- | ------------------------------------------------------- |
| **API Key**                   | Environment variable in `.env` (e.g., `OPENAI_API_KEY`) | Programmatic access, CI/CD, platform API keys           |
| **OAuth (interactive login)** | `/auth login` command in the terminal                   | ChatGPT Plus/Codex plans, Claude Pro, no key management |

<Info>Both methods can coexist. ChatCLI prioritizes OAuth credentials when both are available.</Info>

<Note>
  **OAuth support varies by provider.** Only **OpenAI**, **Anthropic**, and **GitHub Copilot** support OAuth login via `/auth login`. All other providers -- **GoogleAI**, **xAI**, **ZAI**, **MiniMax**, **Moonshot (Kimi)**, **StackSpot**, and **Ollama** -- require authentication via API key (set in `.env` or environment variables).
</Note>

***

## `/auth` Commands

All authentication commands have **auto-completion** — just type `/auth` and press `Tab`.

### View Status

```bash theme={"system"}
/auth status
```

Displays the authentication status of all configured providers, including credential type (API Key vs OAuth), token validity, and source.

### Login via OAuth

```bash theme={"system"}
/auth login openai-codex
/auth login anthropic
/auth login github-copilot
/auth login github-models
```

<Steps>
  <Step title="Browser opens automatically">
    The browser opens to the provider's login page.
  </Step>

  <Step title="Authorize access">
    **OpenAI:** a local HTTP server captures the callback automatically (port **1455**).

    **Anthropic:** after authorizing, copy the code displayed on the page and paste it in the terminal.

    **GitHub Copilot:** enter the device code displayed in the terminal on the GitHub page.
  </Step>

  <Step title="Provider available immediately">
    The provider appears immediately in `/switch` — **no app restart needed**.
  </Step>
</Steps>

#### Supported Providers

| Provider       | Command                      | Compatible Plans                                              |
| -------------- | ---------------------------- | ------------------------------------------------------------- |
| OpenAI         | `/auth login openai-codex`   | ChatGPT Plus, Codex, Team, Enterprise                         |
| Anthropic      | `/auth login anthropic`      | Claude Pro, Team                                              |
| GitHub Copilot | `/auth login github-copilot` | Copilot Individual, Business, Enterprise                      |
| GitHub Models  | `/auth login github-models`  | Any GitHub account (PAT) — [details](/features/github-models) |

### Logout

```bash theme={"system"}
/auth logout openai-codex
/auth logout anthropic
/auth logout github-copilot
```

Removes the stored OAuth credentials for the specified provider.

***

## Technical Details of OAuth Flows

This section describes how each OAuth flow works internally, including URLs, parameters, and ChatCLI behavior.

### Anthropic OAuth (PKCE + Manual Code)

The Anthropic flow uses OAuth 2.0 with PKCE (Proof Key for Code Exchange) and a manual copy-paste mechanism for the authorization code.

<Steps>
  <Step title="PKCE generation and browser launch">
    ChatCLI generates a random `code_verifier` and computes the `code_challenge` using SHA-256 (method `S256`). The browser opens to the authorization URL with the required parameters.
  </Step>

  <Step title="User authorizes in browser">
    The user logs into Claude and authorizes access. The console page displays a code in the format `code#state`.
  </Step>

  <Step title="User pastes code in terminal">
    ChatCLI prompts the user to paste the displayed code. The portion after the `#` is the `state` parameter, used in the token exchange.
  </Step>

  <Step title="Code exchanged for tokens">
    ChatCLI sends the code, state, and code\_verifier to the token endpoint and receives an access\_token + refresh\_token.
  </Step>
</Steps>

| Parameter    | Value                                               |
| ------------ | --------------------------------------------------- |
| Auth URL     | `https://claude.ai/oauth/authorize`                 |
| Token URL    | `https://console.anthropic.com/v1/oauth/token`      |
| Redirect URI | `https://console.anthropic.com/oauth/code/callback` |
| Client ID    | `9d1c250a-e61b-44d9-88ed-5944d1962f5e`              |
| Scopes       | `org:create_api_key user:profile user:inference`    |
| PKCE Method  | `S256` (SHA-256 code challenge)                     |

<Warning>
  The token exchange uses a plain `http.Client` (no custom transport) to avoid TLS fingerprinting issues with Cloudflare. The `User-Agent: claude-cli/2.1.2 (external, cli)` header is required. Using an HTTP client with `LoggingTransport` or a custom transport causes rejection by Anthropic's CDN.
</Warning>

Token refresh also uses the plain `http.Client` and the same `User-Agent`, for the same reason.

***

### OpenAI Codex OAuth (PKCE + Localhost Callback)

The OpenAI flow uses OAuth 2.0 with PKCE and a local HTTP server to automatically capture the callback.

<Steps>
  <Step title="Local server starts on port 1455">
    ChatCLI starts an HTTP server at `http://localhost:1455` to receive the OAuth callback.
  </Step>

  <Step title="Browser opens to authorization page">
    The browser opens to the OpenAI authorization URL with the PKCE challenge and a `state` parameter for CSRF validation.
  </Step>

  <Step title="User authorizes in browser">
    After login and authorization, the browser redirects to `http://localhost:1455/auth/callback` with the authorization code.
  </Step>

  <Step title="CSRF validation and token exchange">
    The local server validates the `state` parameter against the originally generated value (CSRF protection), exchanges the code for tokens, and shuts down the server.
  </Step>
</Steps>

| Parameter    | Value                                                                       |
| ------------ | --------------------------------------------------------------------------- |
| Auth URL     | `https://auth.openai.com/oauth/authorize`                                   |
| Token URL    | `https://auth.openai.com/oauth/token`                                       |
| Redirect URI | `http://localhost:1455/auth/callback`                                       |
| Client ID    | `app_EMoamEEZ73f0CkXaXp7hrann` (overridable via `CHATCLI_OPENAI_CLIENT_ID`) |
| Scopes       | `openid profile email offline_access`                                       |
| PKCE Method  | `S256`                                                                      |

<Tip>Token refresh is automatic and uses the standard HTTP client with logging.</Tip>

***

### GitHub Copilot Device Flow (RFC 8628)

GitHub Copilot uses the **Device Authorization Grant** (RFC 8628), a flow designed for devices without an integrated browser, such as CLI terminals.

<Steps>
  <Step title="Request device code">
    ChatCLI requests a `device_code` and a `user_code` from the GitHub endpoint.
  </Step>

  <Step title="Display code in terminal">
    ChatCLI displays the `user_code` and the verification URL (`https://github.com/login/device`) to the user.
  </Step>

  <Step title="User authorizes in browser">
    The user visits the URL, enters the `user_code`, and authorizes ChatCLI access.
  </Step>

  <Step title="Polling until authorization">
    ChatCLI polls the token endpoint every 5 seconds until it receives the access token or a terminal error.
  </Step>
</Steps>

| Parameter         | Value                                                                |
| ----------------- | -------------------------------------------------------------------- |
| Device Code URL   | `https://github.com/login/device/code`                               |
| Token Polling URL | `https://github.com/login/oauth/access_token`                        |
| Client ID         | `Ov23lifEydOk2Non90tJ` (overridable via `CHATCLI_COPILOT_CLIENT_ID`) |
| Scope             | `read:user`                                                          |
| Max Timeout       | 15 minutes                                                           |

**Polling responses:**

| Response                | Action                             |
| ----------------------- | ---------------------------------- |
| `authorization_pending` | Continue polling normally          |
| `slow_down`             | Increase interval by +5 seconds    |
| `expired_token`         | Fail — the device code has expired |
| `access_denied`         | Fail — the user denied access      |

<Info>GitHub Copilot tokens are non-expiring and do not have a refresh token. They persist until manually revoked on the GitHub settings page.</Info>

***

## Complete Flow: From Zero to First Prompt

If you are starting **without any configured credentials**, follow these steps:

<Steps>
  <Step title="Start ChatCLI">
    Works even without configured credentials.

    ```bash theme={"system"}
    chatcli
    ```
  </Step>

  <Step title="Log in via OAuth">
    ```bash theme={"system"}
    /auth login openai-codex
    ```
  </Step>

  <Step title="Authorize in the browser">
    The browser opens automatically to the provider's login page.
  </Step>

  <Step title="Switch to the provider">
    ```bash theme={"system"}
    /switch
    ```
  </Step>

  <Step title="Send your first prompt">
    ```text theme={"system"}
    Hello, how are you?
    ```
  </Step>
</Steps>

***

## Automatic Endpoint Routing (OpenAI)

ChatCLI automatically detects the credential type and routes requests to the correct endpoint:

| Credential            | Endpoint                                  | API Format                       |
| --------------------- | ----------------------------------------- | -------------------------------- |
| `OPENAI_API_KEY`      | `api.openai.com/v1/responses`             | Responses API (default)          |
| `OPENAI_API_KEY`      | `api.openai.com/v1/chat/completions`      | Chat Completions (legacy models) |
| OAuth (`/auth login`) | `chatgpt.com/backend-api/codex/responses` | Responses API (streaming SSE)    |

<Tip>You don't need to configure anything — routing is automatic based on the token type.</Tip>

<Note>
  On the Codex backend, ChatCLI also sends the client-identification headers the backend requires (`originator` plus a Codex `User-Agent`). Newer model slugs — such as the **GPT-5.6** family (`gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`) — are only served when both headers are present; without them the backend answers **404 Model not found** (Luna, for example, has no server-side fallback).
</Note>

***

## Credential Storage

OAuth credentials are saved with **AES-256-GCM encryption** at:

```text theme={"system"}
~/.chatcli/auth-profiles.json
```

### Encryption Details

| Aspect           | Detail                                         |
| ---------------- | ---------------------------------------------- |
| Algorithm        | AES-256-GCM (authenticated encryption)         |
| Key file         | `~/.chatcli/.auth-key` (32 random bytes)       |
| Key permission   | `0o600` (owner read/write only)                |
| Encrypted format | `chatcli-enc:v1:` + Base64(nonce + ciphertext) |
| Key generation   | Automatic on first use                         |

The encryption key is automatically generated and stored at `~/.chatcli/.auth-key`. Unencrypted data from previous versions is automatically detected (absence of the `chatcli-enc:v1:` prefix) and migrated to the encrypted format on the next save.

Each stored profile contains:

* **Access token** (encrypted)
* **Refresh token** (encrypted, when applicable)
* **Expiry** (timestamp in milliseconds)
* **Account ID** and **email** from the provider
* **Provider type** (anthropic, openai-codex, github-copilot)

To customize the storage directory, set the environment variable:

```bash theme={"system"}
export CHATCLI_AUTH_DIR="~/.config/chatcli/auth"
```

***

## Token Validation

ChatCLI implements strict OAuth token validation to prevent use of invalid or expired credentials:

### Access Token Validation

* **Empty access token rejection** -- Tokens with an empty access token are rejected immediately, without attempting requests. This prevents confusing "unauthorized" errors when credentials are corrupted.
* **Expiry validation** -- The `expiry` field is checked before each request. Expired tokens trigger the refresh flow automatically.

### Maximum Token Lifetime (CHATCLI\_MAX\_TOKEN\_LIFETIME)

For production environments with compliance requirements, you can limit the maximum token lifetime:

```bash theme={"system"}
# Limit tokens to 24 hours (default: 720h / 30 days)
export CHATCLI_MAX_TOKEN_LIFETIME=24h
```

<Info>
  When the maximum lifetime is reached, the token is invalidated and the user must log in again via `/auth login`. This ensures periodic credential rotation even when the refresh token is still valid.
</Info>

| Variable                     | Description                                                                        | Default          |
| ---------------------------- | ---------------------------------------------------------------------------------- | ---------------- |
| `CHATCLI_MAX_TOKEN_LIFETIME` | Maximum lifetime for OAuth/JWT tokens. Accepts Go durations (e.g., `24h`, `168h`). | `720h` (30 days) |

***

## Automatic Token Refresh

ChatCLI automatically renews expired tokens in two layers:

<CardGroup cols={2}>
  <Card title="Proactive Refresh" icon="clock">
    When resolving credentials for a request, ChatCLI checks if the token is expired (`IsExpired()`) or about to expire (`IsExpiringSoon()` with a **5-minute** margin). If so, the refresh is executed **before** the request.
  </Card>

  <Card title="Reactive Refresh" icon="rotate">
    If a request returns an **HTTP 401** error, ChatCLI invalidates the credential cache, performs the token refresh, recreates the HTTP client, and retries the request automatically — all transparent to the user.
  </Card>
</CardGroup>

<Info>The 5-minute safety margin before actual expiry prevents race conditions where the token expires between the check and the request being sent.</Info>

GitHub Copilot tokens (Device Flow) **do not expire** and do not have a refresh token — they persist until manually revoked.

***

## External CLI Sync

ChatCLI can import credentials from external CLIs via `SyncExternalCliCreds()`:

| External CLI         | Paths Checked                                       | Imported Profile       |
| -------------------- | --------------------------------------------------- | ---------------------- |
| **Claude Code**      | `~/.claude/credentials.json` or `~/.claude.json`    | `anthropic-oauth-sync` |
| **OpenAI Codex CLI** | `~/.codex/credentials.json` or `~/.codex/auth.json` | `openai-codex-sync`    |

<Note>Import only occurs if the external credentials are **fresher** than those already stored. This ensures that a login performed directly in ChatCLI is not overwritten by stale data from another CLI.</Note>

***

## Credential Resolution Priority

When determining which credential to use for a provider, ChatCLI follows this priority order:

<Steps>
  <Step title="Auth-profiles store (OAuth/Token)">
    OAuth credentials stored in `auth-profiles.json`, including profiles synced from external CLIs. Automatic refresh is applied at this layer.
  </Step>

  <Step title="Environment variables">
    Variables such as `ANTHROPIC_OAUTH_TOKEN`, `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, and `GITHUB_COPILOT_TOKEN`.
  </Step>
</Steps>

**Prefix system:** credential values may include prefixes that signal the type to the provider:

| Prefix    | Meaning                       |
| --------- | ----------------------------- |
| `oauth:`  | Token obtained via OAuth flow |
| `apikey:` | Traditional API key           |
| `token:`  | Generic token (e.g., Copilot) |

***

### Advanced Configuration

| Variable                    | Description                                                                                  |
| --------------------------- | -------------------------------------------------------------------------------------------- |
| `CHATCLI_OPENAI_CLIENT_ID`  | Allows overriding the OpenAI OAuth client ID                                                 |
| `CHATCLI_COPILOT_CLIENT_ID` | Allows overriding the GitHub Copilot Device Flow client ID (default: `Ov23lifEydOk2Non90tJ`) |
| `COPILOT_API_BASE_URL`      | Copilot API base URL for enterprise environments (default: `https://api.githubcopilot.com`)  |

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Authentication error when clicking the OAuth link (OpenAI)">
    Check that the callback port is not in use by another application. The OpenAI callback server needs port **1455** to capture the return.
  </Accordion>

  <Accordion title="Code does not appear after authorizing (Anthropic)">
    The Anthropic flow redirects to the console page that displays the authorization code in the format `code#state`. Copy **the full text** (including the part after the `#`) and paste it in the terminal when prompted. ChatCLI separates the code from the state automatically.
  </Accordion>

  <Accordion title="Anthropic: 403 error or connection refused during token exchange">
    This typically occurs when a custom HTTP client (with LoggingTransport) is used. ChatCLI uses a plain `http.Client` for Anthropic token exchange to avoid TLS fingerprinting by Cloudflare. If you are developing, ensure that the `exchangeAnthropicToken` function uses a client without a custom transport.
  </Accordion>

  <Accordion title="Provider does not appear in /switch after login">
    Run `/auth status` to verify that the token was saved correctly. If needed, try `/auth logout <provider>` followed by `/auth login <provider>`.
  </Accordion>

  <Accordion title="GitHub Copilot: authorization denied by user">
    Make sure your GitHub account has an active Copilot subscription (Individual, Business, or Enterprise). Check at [https://github.com/settings/copilot](https://github.com/settings/copilot) if Copilot is enabled.
  </Accordion>

  <Accordion title="GitHub Copilot: device code expired">
    The device code has limited validity. If polling exceeds **15 minutes** without authorization, the code expires. Run `/auth login github-copilot` again to generate a new code.
  </Accordion>

  <Accordion title="Expired token">
    OAuth tokens are automatically renewed in two ways:

    * **Proactively**: when resolving credentials, if the token is expired or within the 5-minute margin, a refresh is attempted before the request.
    * **Reactively**: if a request returns a 401 error, ChatCLI invalidates the credential cache, attempts to refresh the OAuth token, recreates the client, and retries the request automatically.

    If both attempts fail (e.g., refresh token is also expired), log in again with `/auth login`.

    GitHub Copilot tokens (Device Flow) do not expire and do not have a refresh token — they are persistent until manually revoked.
  </Accordion>
</AccordionGroup>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Command Reference" icon="rectangle-list" href="/reference/command-reference">
    Complete list of all available commands.
  </Card>

  <Card title="Configuration (.env)" icon="gear" href="/reference/configuration">
    All available environment variables.
  </Card>

  <Card title="Basic Usage" icon="graduation-cap" href="/core-concepts/basic-usage">
    Learn the fundamentals of ChatCLI.
  </Card>
</CardGroup>

***
