How Claude Code and Cursor OAuth Authentication Works in OmniRoute
OmniRoute authenticates Claude Code and Cursor through a PKCE-based OAuth 2.0 flow that generates code verifiers, exchanges authorization codes for tokens, and automatically refreshes access tokens stored in SQLite.
OAuth-based providers in OmniRoute follow a standardized authentication pipeline. This article breaks down how Claude Code and Cursor are authenticated according to the diegosouzapw/OmniRoute source code, including the specific files, methods, and security mechanisms involved.
OAuth Provider Architecture
OmniRoute implements OAuth authentication through a modular provider system. Each provider exports a ZedOAuthProvider configuration that defines authorization endpoints, token URLs, and PKCE requirements.
Provider registration files
- Claude Code →
src/lib/oauth/providers/claude.ts - Cursor →
src/lib/oauth/providers/cursor.ts
Both providers are re-exported through the central catalog at src/lib/oauth/providers/index.ts, making them available to the OAuth runtime.
The Seven-Step OAuth Flow
Step 1: PKCE challenge generation
The flow begins with PKCE (Proof Key for Code Exchange) to prevent authorization code interception attacks. The client generates a cryptographically random code verifier and its SHA-256 code challenge.
// From src/lib/oauth/utils/pkce.ts
import { buildAuthorizationUrl } from '@/lib/oauth/utils/pkce';
async function startOAuth(providerId: 'claude' | 'cursor') {
const provider = oauthProviders[providerId];
const { url, verifier } = buildAuthorizationUrl(provider); // PKCE generation
window.open(url, 'oauth', 'width=500,height=600');
sessionStorage.setItem(`oauthVerifier:${providerId}`, verifier);
}
The buildAuthorizationUrl function in src/lib/oauth/utils/pkce.ts handles:
- Generating the 128-byte random verifier
- Computing the S256 challenge
- Constructing the authorization URL with required parameters
Step 2: Authorization URL construction
OmniRoute builds a provider-specific authorization URL containing:
client_id— fromsrc/lib/oauth/constants/oauth.tsresponse_type=coderedirect_uripointing to/api/oauth/callbackcode_challengeandcode_challenge_method=S256
Step 3: User consent and redirect
The user authenticates directly with the provider:
| Provider | Authorization URL | Token Endpoint |
|---|---|---|
| Claude Code | https://auth.anthropic.com/oauth/authorize |
https://auth.anthropic.com/api/token |
| Cursor | https://cursor.ai/oauth/authorize |
https://cursor.ai/api/token |
Step 4: Authorization code exchange
After user consent, the provider redirects to OmniRoute's callback endpoint with an authorization code. OmniRoute exchanges this code for tokens:
// From src/lib/oauth/tokenRefresh.ts
import { exchangeCodeForToken } from '@/lib/oauth/tokenRefresh';
async function completeOAuth(providerId: 'claude' | 'cursor', code: string) {
const verifier = sessionStorage.getItem(`oauthVerifier:${providerId}`)!;
const tokens = await exchangeCodeForToken(providerId, code, verifier);
// Returns: { accessToken, refreshToken, expiresAt }
}
The exchangeCodeForToken function implements the grant_type=authorization_code request defined in OAuth 2.0 RFC 6749.
Step 5: Token persistence
Tokens are stored via src/lib/oauth/connectionPersistence.ts, which persists to the SQLite oauth_connections table:
// Connection schema (inferred from usage)
interface OAuthConnection {
id: string;
provider: 'claude' | 'cursor';
accessToken: string;
refreshToken: string;
expiresAt: number;
}
Step 6: Automatic token refresh
A background refresh mechanism in src/lib/oauth/tokenRefresh.ts proactively refreshes tokens before expiration:
import { refreshAllOAuthTokens } from '@/lib/oauth/tokenRefresh';
setInterval(() => {
refreshAllOAuthTokens().catch(console.error);
}, 5 * 60 * 1000); // Every 5 minutes
The refreshAllOAuthTokens function iterates through stored connections and uses grant_type=refresh_token for expired or nearing-expiration tokens.
Step 7: Authenticated request execution
Executors read stored tokens and inject them into request headers. The base implementation in src/open-sse/executors/default.ts:
async function addAuthHeader(connectionId: string, headers: Headers) {
const conn = await getOAuthConnection(connectionId); // src/lib/oauth/connectionPersistence.ts
if (conn?.accessToken) {
headers.set('Authorization', `Bearer ${conn.accessToken}`);
}
}
Provider-Specific Implementation Details
Claude Code authentication specifics
Defined in src/lib/oauth/providers/claude.ts:
- Uses Anthropic's OAuth service at
auth.anthropic.com - Requires
anthropic-version: 2023-06-01header on API requests - Strips Anthropic-specific OAuth prefixes from tool names (see test
tool-name-case-preserve-4307.test.ts)
Cursor authentication specifics
Defined in src/lib/oauth/providers/cursor.ts:
- Points to Cursor's OAuth service at
cursor.ai - Supports device-code flow as an alternative to browser-based OAuth
- Uses
src/lib/oauth/utils/grokCliAuthJson.tsto parse JSON auth files for CLI authentication scenarios
UI Integration and User Controls
OAuth providers are exposed to users through:
- Provider enum —
src/shared/constants/providers.tsregistersclaudeandcursorfor routing logic - Settings schema —
src/shared/validation/settingsSchemas.tsenables OAuth toggle controls in the UI - Window management —
src/lib/oauth/utils/ui.tshandles popup creation andpostMessagecallback monitoring
Security Considerations
| Mechanism | Implementation | Purpose |
|---|---|---|
| PKCE | S256 challenge in src/lib/oauth/utils/pkce.ts |
Prevents authorization code interception |
| Verifier storage | sessionStorage (ephemeral) |
Limits exposure window |
| Token storage | SQLite with connection ID isolation | Scoped access per connection |
| Automatic refresh | Background job in tokenRefresh.ts |
Reduces manual re-authentication |
Summary
- PKCE-based flow — All OAuth providers use S256 challenge-response for security
- Provider modules — Claude Code and Cursor each have dedicated files in
src/lib/oauth/providers/ - Centralized utilities — PKCE generation, token refresh, and persistence are shared across providers
- Automatic refresh — Tokens refresh automatically via background job every 5 minutes
- Header injection — Executors retrieve tokens from SQLite and attach
Authorization: Bearerheaders
Frequently Asked Questions
What is PKCE and why does OmniRoute use it?
PKCE (Proof Key for Code Exchange) is an OAuth 2.0 extension that prevents authorization code interception attacks. OmniRoute implements PKCE-S256 in src/lib/oauth/utils/pkce.ts by generating a random verifier, hashing it to create a challenge, and sending only the challenge to the authorization server. The original verifier is required for token exchange, ensuring that even if the authorization code is intercepted, it cannot be redeemed without the verifier.
How does OmniRoute store OAuth tokens securely?
OmniRoute persists tokens through src/lib/oauth/connectionPersistence.ts to a local SQLite database in the oauth_connections table. The ephemeral PKCE verifier is stored only in sessionStorage and discarded after token exchange. Access tokens and refresh tokens are scoped per connection ID and retrieved only when executing requests for that specific connection.
Can OmniRoute refresh expired Claude Code or Cursor tokens automatically?
Yes. The src/lib/oauth/tokenRefresh.ts module runs a background refresh job that checks token expiration timestamps and proactively refreshes tokens using grant_type=refresh_token before they expire. This eliminates manual re-authentication for long-running sessions.
Does Cursor support authentication methods other than browser OAuth?
Yes. Cursor additionally supports device-code flow for CLI environments. The helper src/lib/oauth/utils/grokCliAuthJson.ts parses JSON authentication files and converts them into token requests, enabling headless authentication scenarios without browser interaction.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →