OAuth Authentication Guide

July 17, 2026 · View on GitHub

VT Code supports secure OAuth 2.0 authentication for multiple AI providers, enabling seamless account-based access without managing API keys directly.

Overview

OAuth integration in VT Code provides:

  • PKCE-Secured Flows: RFC 7636 Proof Key for Code Exchange for client-only applications
  • Secure Token Storage: OS-native credential storage (Keychain, Credential Manager, Secret Service)
  • Automatic Token Refresh: Seamless token renewal without user intervention
  • Multi-Provider Support: GitHub Copilot, OpenAI ChatGPT, and OpenRouter
  • Managed Auth Delegation: GitHub Copilot authentication delegated to official copilot CLI
  • Fallback Encryption: AES-256-GCM encrypted file storage when keyring unavailable

Supported Providers

GitHub Copilot Managed Auth

VT Code uses the official copilot CLI for GitHub Copilot authentication. No separate OAuth flow is implemented — instead, authentication is delegated to the official copilot binary.

Setup

# Install GitHub Copilot CLI
# See: https://docs.github.com/en/copilot/how-tos/copilot-cli/install-copilot-cli

# Authenticate via the official copilot CLI
copilot login

# Or use VT Code's TUI
vtcode
# Then use: /login copilot

How it Works:

  • VT Code shells out to copilot binary for device-flow authentication
  • copilot login launches browser-based device flow (GitHub handles auth)
  • Credentials stored securely by the copilot CLI (platform-native keyring)
  • VT Code probes auth status and launches ACP session without managing tokens

Requirements

  • Required: copilot CLI on PATH (install via GitHub's official guide)
  • Optional: gh (GitHub CLI) — only used as fallback if already authenticated
  • Active GitHub Copilot subscription

Configuration

In vtcode.toml:

[auth.copilot]
# Optional: Point to custom copilot binary if not on PATH
command = "/path/to/copilot"

Or via environment variable:

export VTCODE_COPILOT_COMMAND="/path/to/copilot"

Device Flow Authentication

When you run /login copilot:

  1. VT Code invokes copilot login
  2. copilot outputs a user code (e.g., ABCD-EFGH)
  3. Browser opens to github.com/login/device
  4. You enter the code and authorize
  5. copilot stores credentials in OS keyring
  6. VT Code detects auth status and activates Copilot provider

Token Management

Tokens are managed entirely by the copilot CLI:

# Check authentication status
vtcode auth status copilot

# Logout
vtcode logout copilot
# Or: /logout copilot

Troubleshooting

copilot command not found:

# Install copilot CLI
# macOS:
brew install gh-copilot

# Or download from GitHub:
# https://github.com/github/copilot-cli/releases

VT Code doesn't detect auth:

# Manually authenticate via copilot CLI first
copilot login

# Then check VT Code's detection
vtcode auth status copilot

gh not found (optional):

  • This is only a fallback; it's not required
  • copilot works independently for auth
  • VT Code only uses gh to detect existing auth sessions

OpenAI ChatGPT OAuth

Authenticate with your OpenAI account to use ChatGPT models.

Setup

# Launch VT Code
vtcode

# Use the OAuth flow within the TUI
# VT Code will open your browser for OpenAI authentication

Authentication Methods:

  • OAuth (recommended): Automatic browser-based login
  • API Key: Direct API key entry
  • Manual Callback: Paste authorization code manually

Configuration

In vtcode.toml:

[llm.openai]
# OAuth settings
preferred_auth_method = "oauth"  # "oauth", "api_key", or "manual_callback"

# Optional: Specify callback port for manual OAuth flow
auth_callback_port = 8080

[auth.openai]
# Control where tokens are stored
credentials_store_mode = "keyring"  # "keyring" or "file"

Token Storage

Default (Keyring):

  • macOS: Keychain
  • Windows: Credential Manager
  • Linux: Secret Service API / libsecret

Fallback (Encrypted Files):

  • Location: ~/.vtcode/auth/openai_chatgpt.json
  • Encryption: AES-256-GCM with machine-derived key
  • Automatic migration from file-based to keyring storage

Troubleshooting

Keyring unavailable on Linux:

# Install a keyring daemon (e.g., gnome-keyring)
sudo apt-get install gnome-keyring

# Or use file-based storage
[auth.openai]
credentials_store_mode = "file"

Clear OAuth Session:

# Remove stored tokens (interactive prompt will request fresh auth)
vtcode auth clear openai

OpenRouter OAuth

Authenticate with OpenRouter for access to multiple model providers.

Setup

# Launch VT Code with OpenRouter OAuth
vtcode

# Enable OAuth in the provider selection flow

PKCE Flow:

  • Secure authorization without client secrets
  • Callback server runs on localhost:8484 (configurable)
  • Browser-based authentication

Configuration

In vtcode.toml:

[llm.openrouter]
use_oauth = true               # Enable OAuth flow
auto_refresh = true            # Automatically refresh tokens
flow_timeout_secs = 300        # Browser flow timeout

[auth.openrouter]
callback_port = 8484           # Local OAuth callback server port
credentials_store_mode = "keyring"

Token Storage

Same as OpenAI:

  • Keyring: Platform-native credential store (default)
  • Fallback: ~/.vtcode/auth/openrouter.json (AES-256-GCM encrypted)

Refresh Tokens

OpenRouter tokens are automatically refreshed based on expiration:

// Automatic in production; refresh happens transparently
refresh_token_if_needed(&mut token_storage)?;

Security Model

Authentication Architecture

User Request

[Check Stored Token] ← Keyring (primary)
    ↓                   ← Encrypted File (fallback)
[Token Valid?]
    ├─ Yes → Use Token
    └─ No  → [PKCE OAuth Flow]

            [Browser Auth]

            [Callback Server]

            [Token Exchange]

            [Secure Storage]

Key Management

Machine-Derived Encryption Key (file storage fallback):

  • Based on: hostname + user ID + static salt
  • Algorithm: PBKDF2 (SHA-256)
  • Cipher: AES-256-GCM (AEAD)

No Plain Text:

  • Tokens never stored unencrypted
  • Keyring data encrypted at OS level
  • Encrypted files use authenticated encryption

PKCE Security

Implements RFC 7636 requirements:

  • Code Challenge: SHA-256 hash of 128-byte random verifier
  • No Client Secret: Suitable for public/native clients
  • Protected from CSRF: State parameter included in flow

CLI Usage

Interactive Mode

vtcode

Follow the provider selection flow; OAuth authentication triggers automatically when enabled.

Token Management

# View current auth status
vtcode auth status <provider>

# Clear authentication
vtcode auth clear <provider>

# Re-authenticate
vtcode auth refresh <provider>

Supported providers: copilot, openai, openrouter

GitHub Copilot note: Tokens are managed by the copilot CLI and stored in the OS keyring. VT Code probes status but does not store credentials directly.

Token Lifecycle

Acquisition

  1. User selects OAuth provider
  2. PKCE challenge generated (128-byte random verifier)
  3. Browser opens to provider authorization page
  4. User grants permission
  5. Code exchanged for token
  6. Token stored securely

Refresh

  1. Token checked before use
  2. If expired, automatic refresh attempted
  3. New token stored, old token discarded
  4. If refresh fails, user prompted for re-authentication

Expiration

  • OpenAI: 30-day expiration
  • OpenRouter: Provider-dependent
  • Grace period: 5 minutes (token considered expired 5 min before actual expiration)

Troubleshooting

"Keyring not available"

Linux: Install and start a keyring daemon:

sudo apt-get install gnome-keyring
# Or use KDE Wallet, pass, etc.

All Platforms: Use file storage:

[auth.openai]
credentials_store_mode = "file"

"Token exchange failed"

  1. Check internet connection
  2. Verify provider's OAuth service is operational
  3. Ensure callback port (8080/8484) is not blocked by firewall
  4. Try clearing session and re-authenticating:
    vtcode auth clear openai
    vtcode
    

"Browser didn't open"

Manual callback flow:

  1. Copy the authorization URL
  2. Open manually in browser
  3. Paste the authorization code back into VT Code

Environment Variables

Control OAuth behavior via env vars:

# OpenAI OAuth
export OPENAI_OAUTH_CLIENT_ID="your-client-id"
export OPENAI_OAUTH_REDIRECT_URI="http://localhost:8080/auth/callback"
export OPENAI_PREFERRED_AUTH_METHOD="oauth"

# OpenRouter OAuth
export OPENROUTER_USE_OAUTH="true"
export OPENROUTER_CALLBACK_PORT="8484"

# Token storage
export VTCODE_AUTH_STORE_MODE="keyring"  # or "file"

Development

Testing OAuth Flows

// Example: Testing OpenRouter OAuth
use vtcode_auth::{
    get_auth_url,
    exchange_code_for_token,
    AuthCredentialsStoreMode,
};

// Get authorization URL
let (auth_url, verifier) = get_auth_url()?;
println!("Visit: {}", auth_url);

// Exchange authorization code for token
let token = exchange_code_for_token(
    code,
    &verifier,
    AuthCredentialsStoreMode::Keyring
)?;

Adding a New OAuth Provider

  1. Create provider module: src/oauth_<provider>.rs
  2. Implement PKCE flow: Use generate_pkce_challenge()
  3. Token exchange: Implement code ↔ token exchange
  4. Storage: Use CredentialStorage for secure storage
  5. Configuration: Add provider config to AuthConfig

See crates/codegen/vtcode-auth/src/openrouter_oauth.rs for a reference implementation.

See Also