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

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 — from src/lib/oauth/constants/oauth.ts
  • response_type=code
  • redirect_uri pointing to /api/oauth/callback
  • code_challenge and code_challenge_method=S256

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-01 header 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.ts to parse JSON auth files for CLI authentication scenarios

UI Integration and User Controls

OAuth providers are exposed to users through:

  1. Provider enumsrc/shared/constants/providers.ts registers claude and cursor for routing logic
  2. Settings schemasrc/shared/validation/settingsSchemas.ts enables OAuth toggle controls in the UI
  3. Window managementsrc/lib/oauth/utils/ui.ts handles popup creation and postMessage callback 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: Bearer headers

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →