Setting Up OAuth Providers (Claude Code, Codex, Cursor, Cline) with OmniRoute

OmniRoute unifies Claude Code, Codex, Cursor, and Cline behind a single OAuth 2.0 architecture that stores access tokens in a local connections database and routes requests through provider-specific executors and translators.

OmniRoute treats every upstream model as a provider that can authenticate via OAuth 2.0 or API keys. Setting up OAuth providers for Claude Code, Codex, Cursor, and Cline involves registering provider metadata, configuring environment variables, and initiating the authorization flow through generic API endpoints that handle token exchange and storage automatically.

Provider Registration and OAuth Configuration

Each provider starts with a registration entry in the provider catalog. OmniRoute defines compatibility prefixes in src/shared/constants/providers.ts (such as CLAUDE_CODE_COMPATIBLE_PREFIX) and maintains concrete registry entries under open-sse/config/providers/registry/.

The OAuth-specific metadata for each provider lives in dedicated modules under src/lib/oauth/providers/:

These modules export a consistent interface used by the generic OAuth router to build authorization URLs and exchange codes for tokens.

Environment Variable Setup

Before initiating flows, configure the OAuth credentials in your environment. Create or update your .env file with the client ID and secret pairs for each provider:


# Claude Code OAuth credentials

CLAUDE_CODE_CLIENT_ID=your-claude-code-client-id
CLAUDE_CODE_CLIENT_SECRET=your-claude-code-secret

# Codex OAuth credentials

CODEX_CLIENT_ID=your-codex-client-id
CODEX_CLIENT_SECRET=your-codex-secret

# Cursor OAuth credentials

CURSOR_CLIENT_ID=your-cursor-client-id
CURSOR_CLIENT_SECRET=your-cursor-secret

# Cline OAuth credentials

CLINE_CLIENT_ID=your-cline-client-id
CLINE_CLIENT_SECRET=your-cline-secret

Default redirect URIs and optional overrides are centralized in src/lib/oauth/constants/oauth.ts. After updating environment variables, restart OmniRoute (npm run dev or npm run start) to load the new configuration into the provider modules.

Initiating the OAuth Flow

All OAuth actions—start, callback, poll, and revoke—are handled by the dynamic route in src/app/api/oauth/[provider]/[action]/route.ts. This route delegates to provider-specific helpers (generateAuthData, getProvider) to construct the proper authorization URL and exchange codes for access tokens.

Starting Authorization

Initiate the OAuth flow by calling the start endpoint for your chosen provider:


# Claude Code

curl -X GET "http://localhost:3000/api/oauth/claude/start?redirect_uri=http://localhost:3000/callback"

# Cursor

curl -X GET "http://localhost:3000/api/oauth/cursor/start?redirect_uri=http://localhost:3000/callback"

OmniRoute returns a 302 redirect to the provider's consent page. After you grant permission, the provider redirects back to your specified callback URI with an authorization code.

Handling the Callback

The provider redirects to /api/oauth/<provider>/callback with the authorization code. OmniRoute automatically exchanges this code for an access token using the provider's tokenUrl and stores the result:

curl -X POST "http://localhost:3000/api/oauth/claude/callback" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "AUTH_CODE_FROM_REDIRECT",
    "redirect_uri": "http://localhost:3000/callback"
  }'

On success, the response includes a connectionId referencing the newly created OAuth connection in the database.

Token Storage and Request Execution

Once obtained, access tokens are persisted in the connections table with authType: "oauth". When you reference a connection in subsequent API calls, OmniRoute retrieves the token and routes the request through the appropriate executor.

Executors and Translators

Request execution depends on the provider type:

The translation layer converts OpenAI-style requests to provider-native formats:

Making Authenticated Requests

Use the stored connection to route chat completions through your OAuth provider:

curl -X POST "http://localhost:3000/api/v1/chat/completions" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-4",
    "messages": [{"role": "user", "content": "Hello!"}],
    "connectionId": "your-connection-id-from-callback"
  }'

OmniRoute automatically attaches the OAuth token via the provider's executor and translates the response back to OpenAI-compatible JSON.

Usage Monitoring and CLI Integration

Each OAuth provider implements quota retrieval through usage fetchers. For example, src/lib/oauth/providers/cursor.ts exports a fetchUsage function that calls GET /api/oauth/usage to check remaining credits.

Check usage programmatically:

curl -X GET "http://localhost:3000/api/oauth/usage?provider=cursor&connectionId=your-connection-id"

CLI Shortcuts

OmniRoute includes a test CLI (src/cli/omni.ts) that simplifies OAuth flows:


# Start Claude Code OAuth flow

npm run cli -- oauth claude start

# Exchange code after browser redirect

npm run cli -- oauth claude callback <AUTH_CODE>

Complete Integration Example

Below is a TypeScript implementation demonstrating the full OAuth lifecycle:

import fetch from "node-fetch";

// Step 1: Initiate OAuth flow
async function startOAuth(provider: string) {
  const res = await fetch(
    `http://localhost:3000/api/oauth/${provider}/start?redirect_uri=http://localhost:3000/callback`,
    { redirect: "manual" }
  );
  const location = res.headers.get("location");
  console.log(`Authorize ${provider} at:`, location);
}

// Step 2: Exchange authorization code for token
async function exchangeCode(provider: string, code: string) {
  const res = await fetch(
    `http://localhost:3000/api/oauth/${provider}/callback`,
    {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ 
        code, 
        redirect_uri: "http://localhost:3000/callback" 
      }),
    }
  );
  const body = await res.json();
  console.log(`Connection created:`, body.connectionId);
  return body.connectionId;
}

// Step 3: Make authenticated request
async function chatWithProvider(connectionId: string) {
  const res = await fetch("http://localhost:3000/api/v1/chat/completions", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      model: "claude-opus-4",
      messages: [{ role: "user", content: "Explain OAuth flows" }],
      connectionId,
    }),
  });
  const reply = await res.json();
  console.log("Response:", reply.choices?.[0]?.message?.content);
}

// Usage: await startOAuth("claude"); 
// Then: await exchangeCode("claude", "CODE_FROM_QUERY");
// Finally: await chatWithProvider("CONNECTION_ID");

Summary

Frequently Asked Questions

How does OmniRoute store OAuth tokens securely?

OmniRoute stores OAuth access tokens in the local connections table (managed via src/lib/db/connections.ts) with authType set to "oauth". Tokens are retrieved by connectionId during request execution and injected into the Authorization: Bearer header by the appropriate executor. The system does not log or expose tokens in API responses after the initial callback exchange.

Can I use the same OAuth application for multiple users?

Yes. Each authorization flow creates a distinct row in the connections table with a unique connectionId. Multiple users can authorize the same provider application (using the same CLIENT_ID and CLIENT_SECRET), and OmniRoute maintains separate tokens for each user through distinct connection records. When making requests, specify the appropriate connectionId to route through the correct user's OAuth context.

Why does Claude Code require a special executor while Cursor uses the default?

Claude Code requires specific headers—including anthropic-beta and a custom User-Agent (claude-cli/${CLAUDE_CODE_VERSION})—that differ from standard OpenAI-compatible implementations. The ClaudeExecutor in open-sse/executors/claudeIdentity.ts handles these requirements, while Cursor, Codex, and Cline expose OpenAI-compatible endpoints that work with the DefaultExecutor in open-sse/executors/default.ts using standard bearer token authentication.

How do I revoke an OAuth connection?

Send a request to the revoke endpoint for the specific provider and connection:

curl -X POST "http://localhost:3000/api/oauth/claude/revoke" \
  -H "Content-Type: application/json" \
  -d '{"connectionId": "your-connection-id"}'

This removes the token from the connections table and calls the provider's revocation endpoint if configured in the provider's OAuth module (such as src/lib/oauth/providers/claude.ts).

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 →