How to Set Up OAuth Providers for Claude Code, Codex, and Antigravity in OmniRoute

OmniRoute provides a unified OAuth framework in src/lib/oauth/providers/ that supports multiple authentication flows—including Authorization-Code PKCE for Claude Code and Codex, Import-Token for Cursor, and standard Authorization-Code for Antigravity—allowing you to configure these providers through environment variables and standardized provider modules.

Setting up OAuth providers in the diegosouzapw/OmniRoute repository involves configuring environment variables and leveraging provider-specific modules that handle distinct authentication flows. Each provider resides in src/lib/oauth/providers/ and implements a consistent contract including buildAuthUrl, exchangeToken, and mapTokens functions. This guide covers the exact implementation details for Claude Code, Codex, Cursor, and Antigravity, including configuration constants from src/lib/oauth/constants/oauth.ts and runnable code examples.

OmniRoute OAuth Architecture Overview

Every provider in OmniRoute follows a strict contract defined in src/lib/oauth/constants/oauth.ts. Each implementation exports a config object containing URLs, scopes, and client credentials, plus five core functions:

  • buildAuthUrl – Generates the authorization URL with state and PKCE parameters where applicable.
  • exchangeToken – Swaps the authorization code for access tokens via HTTP POST.
  • postExchange – Optional hook for fetching additional data (user info, bootstrap data) after token exchange.
  • mapTokens – Normalizes the raw OAuth response into OmniRoute’s internal shape (accessToken, refreshToken, expiresIn, email, providerSpecificData).

The PROVIDERS enum in constants/oauth.ts registers each provider, while environment variables like CLAUDE_OAUTH_CLIENT_ID and ANTIGRAVITY_OAUTH_CLIENT_SECRET inject credentials securely.

Claude Code OAuth Configuration

Claude Code (Anthropic) uses the Authorization-Code PKCE flow defined in src/lib/oauth/providers/claude.ts. This implementation requires a code verifier and challenge to secure the token exchange.

Key Configuration Constants

// src/lib/oauth/constants/oauth.ts
export const CLAUDE_CONFIG = {
  clientId: resolvePublicCred("claude_id", "CLAUDE_OAUTH_CLIENT_ID"),
  authorizeUrl: "https://claude.ai/oauth/authorize",
  tokenUrl: "https://api.anthropic.com/v1/oauth/token",
  redirectUri: process.env.CLAUDE_CODE_REDIRECT_URI || "https://platform.claude.com/oauth/code/callback",
  scopes: [
    "org:create_api_key",
    "user:profile",
    "user:inference",
    "user:sessions:claude_code",
    "user:mcp_servers",
  ],
  codeChallengeMethod: "S256",
};

Implementation Flow

  1. Generate PKCE pair using generatePkcePair() from @omniroute/open-sse/utils/pkce.ts.
  2. Build auth URL via claude.buildAuthUrl(config, redirectUri, state, codeChallenge).
  3. Exchange code after user redirect using claude.exchangeToken(config, code, redirectUri, codeVerifier, state).
  4. Bootstrap account data via claude.postExchange(rawTokens), which fetches from https://api.anthropic.com/api/claude_cli/bootstrap.
  5. Map tokens to OmniRoute format using claude.mapTokens(rawTokens, extra), which injects a random cliUserID.

Code Example

import { claude } from "./src/lib/oauth/providers/claude.ts";
import { CLAUDE_CONFIG } from "./src/lib/oauth/constants/oauth.ts";
import { generatePkcePair } from "@omniroute/open-sse/utils/pkce.ts";

async function loginClaude() {
  const { verifier, challenge } = await generatePkcePair();
  const state = crypto.randomUUID();
  const authUrl = claude.buildAuthUrl(
    CLAUDE_CONFIG,
    CLAUDE_CONFIG.redirectUri,
    state,
    challenge,
  );
  console.log("Open this URL:", authUrl);

  // After user authorization, capture the code from redirect
  const code = await waitForCodeFromRedirect();

  const rawTokens = await claude.exchangeToken(
    CLAUDE_CONFIG,
    code,
    CLAUDE_CONFIG.redirectUri,
    verifier,
    state,
  );

  const extra = await claude.postExchange(rawTokens);
  const omniTokens = claude.mapTokens(rawTokens, extra);
  console.log("OmniRoute token:", omniTokens);
}

Codex OAuth Configuration

Codex (OpenAI) also uses Authorization-Code PKCE but adds query parameters to force fresh authentication, enabling multi-account login support. The implementation lives in src/lib/oauth/providers/codex.ts.

Key Configuration Constants

// src/lib/oauth/constants/oauth.ts
export const CODEX_CONFIG = {
  clientId: resolvePublicCred("codex_id", "CODEX_OAUTH_CLIENT_ID"),
  authorizeUrl: "https://auth.openai.com/oauth/authorize",
  tokenUrl: "https://auth.openai.com/oauth/token",
  scope: "openid profile email offline_access",
  codeChallengeMethod: "S256",
  extraParams: {
    id_token_add_organizations: "true",
    codex_cli_simplified_flow: "true",
    originator: "codex_cli_rs",
    prompt: "login", // Forces login screen for account switching
  },
};

Implementation Differences

Unlike Claude, Codex does not use postExchange. The mapTokens function parses the id_token JWT directly to extract workspace data. The extraParams are automatically appended to the authorization URL by buildAuthUrl.

Code Example

import { codex } from "./src/lib/oauth/providers/codex.ts";
import { CODEX_CONFIG } from "./src/lib/oauth/constants/oauth.ts";
import { generatePkcePair } from "@omniroute/open-sse/utils/pkce.ts";

async function loginCodex() {
  const { verifier, challenge } = await generatePkcePair();
  const state = crypto.randomUUID();

  // extraParams injected automatically
  const authUrl = codex.buildAuthUrl(
    CODEX_CONFIG,
    CODEX_CONFIG.redirectUri ?? "",
    state,
    challenge,
  );
  console.log("Visit:", authUrl);

  const code = await waitForCodeFromRedirect();

  const rawTokens = await codex.exchangeToken(
    CODEX_CONFIG,
    code,
    CODEX_CONFIG.redirectUri ?? "",
    verifier,
    state,
  );

  // No postExchange needed
  const omniTokens = codex.mapTokens(rawTokens, {});
  console.log(omniTokens);
}

Cursor Import-Token Configuration

Cursor uses a unique Import-Token flow rather than web-based OAuth. The provider reads credentials directly from the Cursor IDE's local SQLite database. Implementation: src/lib/oauth/providers/cursor.ts.

Key Configuration Constants

// src/lib/oauth/constants/oauth.ts
export const CURSOR_CONFIG = {
  apiEndpoint: "https://api2.cursor.sh",
  clientVersion: "3.2.14",
  clientType: "ide",
  tokenStoragePaths: {
    linux: "~/.config/Cursor/User/globalStorage/state.vscdb",
    macos: "/Users/<user>/Library/Application Support/Cursor/User/globalStorage/state.vscdb",
    windows: "%APPDATA%\\Cursor\\User\\globalStorage\\state.vscdb",
  },
  dbKeys: {
    accessToken: "cursorAuth/accessToken",
    machineId: "storage.serviceMachineId",
  },
};

Database Extraction Flow

  1. Locate SQLite DB at OS-specific path (e.g., ~/.config/Cursor/User/globalStorage/state.vscdb on Linux).
  2. Query keys cursorAuth/accessToken and storage.serviceMachineId.
  3. Map tokens via cursor.mapTokens() with a default 24-hour expiration.

Code Example

import { cursor } from "./src/lib/oauth/providers/cursor.ts";
import { CURSOR_CONFIG } from "./src/lib/oauth/constants/oauth.ts";
import sqlite3 from "better-sqlite3";
import os from "node:os";

function getCursorDbPath(): string {
  const platform = os.platform();
  const base = CURSOR_CONFIG.tokenStoragePaths[
    platform === "win32" ? "windows" : platform === "darwin" ? "macos" : "linux"
  ];
  return base.replace("<user>", os.userInfo().username);
}

function importCursorToken() {
  const dbPath = getCursorDbPath();
  const db = sqlite3(dbPath, { readonly: true });
  
  const token = db
    .prepare(`SELECT value FROM ItemTable WHERE key = ?`)
    .get(CURSOR_CONFIG.dbKeys.accessToken);
  const machine = db
    .prepare(`SELECT value FROM ItemTable WHERE key = ?`)
    .get(CURSOR_CONFIG.dbKeys.machineId);

  const omniTokens = cursor.mapTokens({
    accessToken: token?.value,
    machineId: machine?.value,
    expiresIn: 86400, // ~24 hours
  });

  console.log("Cursor token:", omniTokens);
}

Antigravity OAuth Configuration

Antigravity uses Authorization-Code flow without PKCE (Google native flow). The implementation in src/lib/oauth/providers/antigravity.ts includes a post-exchange step that fetches user information and Code-Assist endpoints.

Key Configuration Constants

// src/lib/oauth/constants/oauth.ts
export const ANTIGRAVITY_CONFIG = {
  clientId: resolvePublicCred("antigravity_id", "ANTIGRAVITY_OAUTH_CLIENT_ID"),
  clientSecret: resolvePublicCred("antigravity_alt", "ANTIGRAVITY_OAUTH_CLIENT_SECRET"),
  authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
  tokenUrl: "https://oauth2.googleapis.com/token",
  userInfoUrl: "https://www.googleapis.com/oauth2/v1/userinfo",
  scopes: [
    "https://www.googleapis.com/auth/cloud-platform",
    "https://www.googleapis.com/auth/userinfo.email",
    "https://www.googleapis.com/auth/userinfo.profile",
    "https://www.googleapis.com/auth/cclog",
  ],
};

Implementation Flow

  1. Build auth URL without required PKCE (optional challenge ignored by service).
  2. Exchange token using client_secret in the POST body.
  3. Post-exchange fetches userInfo and calls loadCodeAssistEndpoints() to retrieve additional metadata.
  4. Map tokens extracts projectId, tier, and email from the extra data.

Code Example

import { antigravity } from "./src/lib/oauth/providers/antigravity.ts";
import { ANTIGRAVITY_CONFIG } from "./src/lib/oauth/constants/oauth.ts";

async function loginAntigravity() {
  const state = crypto.randomUUID();
  
  // PKCE optional for Antigravity
  const authUrl = antigravity.buildAuthUrl(
    ANTIGRAVITY_CONFIG,
    "http://localhost:3000/oauth/callback",
    state,
  );

  console.log("Open:", authUrl);
  const code = await waitForCodeFromRedirect();

  const rawTokens = await antigravity.exchangeToken(
    ANTIGRAVITY_CONFIG,
    code,
    "http://localhost:3000/oauth/callback",
  );

  const extra = await antigravity.postExchange(rawTokens);
  const omniTokens = antigravity.mapTokens(rawTokens, extra);
  console.log("Antigravity token:", omniTokens);
}

Using the Generic OAuth API

OmniRoute exposes a Next.js route that abstracts provider-specific implementations:


POST /api/oauth/[provider]/[action]

  • provider – Enum value from PROVIDERS (claude, codex, cursor, antigravity).
  • actionauthorize (returns URL), exchange (trades code for tokens), or import (for Cursor).

Example request for Claude:

curl -X POST https://your-app.com/api/oauth/claude/authorize \
  -H "Content-Type: application/json" \
  -d '{"redirectUri":"https://myapp.local/callback"}'

Response:

{
  "authUrl": "https://claude.ai/oauth/authorize?...&code_challenge=..."
}

Summary

  • Claude Code requires the PKCE flow with codeChallengeMethod: "S256" and a post-exchange bootstrap call to https://api.anthropic.com/api/claude_cli/bootstrap.
  • Codex uses PKCE with extraParams.prompt=login to enable multi-account authentication and skips post-exchange processing by parsing the JWT directly.
  • Cursor bypasses web OAuth entirely, reading the accessToken and machineId from the local SQLite database at state.vscdb.
  • Antigravity uses standard Authorization-Code without PKCE, requiring client_secret and implementing postExchange to fetch Google user info and Code-Assist endpoints.
  • All providers normalize data through mapTokens() and store configuration in src/lib/oauth/constants/oauth.ts.

Frequently Asked Questions

What is the difference between PKCE and Import-Token flows in OmniRoute?

PKCE (Proof Key for Code Exchange) is used by Claude Code and Codex to secure public clients by verifying the code challenge during token exchange, preventing authorization code interception attacks. Import-Token, used exclusively by Cursor, reads existing credentials from the local filesystem rather than initiating a web flow, as the token is already stored in the Cursor IDE's SQLite database.

How do I handle environment variables for multiple OAuth providers?

Each provider uses a specific environment variable pattern defined in resolvePublicCred() calls within src/lib/oauth/constants/oauth.ts. For Claude, set CLAUDE_OAUTH_CLIENT_ID; for Codex, CODEX_OAUTH_CLIENT_ID; for Antigravity, both ANTIGRAVITY_OAUTH_CLIENT_ID and ANTIGRAVITY_OAUTH_CLIENT_SECRET. Cursor requires no environment variables since it imports tokens from the local database.

Why does Antigravity not require PKCE while Claude and Codex do?

Antigravity uses Google's OAuth 2.0 implementation which supports confidential clients with client_secret authentication, making PKCE optional. Claude and Codex implement OAuth for public clients (CLI tools) where the client cannot securely store a secret, making PKCE mandatory to protect the authorization code during the exchange process.

Can I customize the redirect URI for these OAuth providers?

Yes, all providers support custom redirect URIs through the redirectUri parameter in buildAuthUrl() or the redirectUri field in the provider config objects. For Claude, you can also set the CLAUDE_CODE_REDIRECT_URI environment variable to override the default https://platform.claude.com/oauth/code/callback.

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 →