# How Claude Code and Cursor OAuth Authentication Works in OmniRoute

> Learn how OmniRoute secures Claude Code and Cursor with PKCE OAuth 2.0. Understand code verifiers, token exchange, and automatic access token refresh in SQLite.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: internals
- Published: 2026-08-04

---

**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`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/oauth/providers/claude.ts)
- **Cursor** → [`src/lib/oauth/providers/cursor.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/oauth/providers/cursor.ts)

Both providers are re-exported through the central catalog at [`src/lib/oauth/providers/index.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/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*.

```typescript
// 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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/oauth/constants/oauth.ts)
- `response_type=code`
- `redirect_uri` pointing to `/api/oauth/callback`
- `code_challenge` and `code_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:

```typescript
// 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`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/oauth/connectionPersistence.ts), which persists to the SQLite `oauth_connections` table:

```typescript
// 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`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/oauth/tokenRefresh.ts) proactively refreshes tokens before expiration:

```typescript
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`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/open-sse/executors/default.ts):

```typescript
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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/tool-name-case-preserve-4307.test.ts))

### Cursor authentication specifics

Defined in [`src/lib/oauth/providers/cursor.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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 enum** — [`src/shared/constants/providers.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/shared/constants/providers.ts) registers `claude` and `cursor` for routing logic
2. **Settings schema** — [`src/shared/validation/settingsSchemas.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/shared/validation/settingsSchemas.ts) enables OAuth toggle controls in the UI
3. **Window management** — [`src/lib/oauth/utils/ui.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/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`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/oauth/utils/grokCliAuthJson.ts) parses JSON authentication files and converts them into token requests, enabling headless authentication scenarios without browser interaction.