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
copilotCLI - 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
copilotbinary for device-flow authentication copilot loginlaunches browser-based device flow (GitHub handles auth)- Credentials stored securely by the
copilotCLI (platform-native keyring) - VT Code probes auth status and launches ACP session without managing tokens
Requirements
- Required:
copilotCLI onPATH(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:
- VT Code invokes
copilot login copilotoutputs a user code (e.g.,ABCD-EFGH)- Browser opens to
github.com/login/device - You enter the code and authorize
copilotstores credentials in OS keyring- 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
copilotworks independently for auth- VT Code only uses
ghto 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
- User selects OAuth provider
- PKCE challenge generated (128-byte random verifier)
- Browser opens to provider authorization page
- User grants permission
- Code exchanged for token
- Token stored securely
Refresh
- Token checked before use
- If expired, automatic refresh attempted
- New token stored, old token discarded
- 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"
- Check internet connection
- Verify provider's OAuth service is operational
- Ensure callback port (8080/8484) is not blocked by firewall
- Try clearing session and re-authenticating:
vtcode auth clear openai vtcode
"Browser didn't open"
Manual callback flow:
- Copy the authorization URL
- Open manually in browser
- 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
- Create provider module:
src/oauth_<provider>.rs - Implement PKCE flow: Use
generate_pkce_challenge() - Token exchange: Implement code ↔ token exchange
- Storage: Use
CredentialStoragefor secure storage - Configuration: Add provider config to
AuthConfig
See crates/codegen/vtcode-auth/src/openrouter_oauth.rs for a reference implementation.