How to Configure OAuth for Claude Code, Codex, Cursor and Antigravity in OmniRoute
To configure OAuth for Claude Code, Codex, Cursor, and Antigravity in OmniRoute, set the provider-specific environment variables for each service, then invoke the buildAuthUrl, exchangeToken, and mapTokens functions from their respective modules in src/lib/oauth/providers/ to handle PKCE challenges, token exchanges, and response normalization.
OmniRoute unifies authentication for multiple AI coding assistants through a standard provider interface. Each integration lives in src/lib/oauth/providers/ and follows a consistent contract defined in src/lib/oauth/constants/oauth.ts, supporting authorization_code_pkce, authorization_code, and import_token flows.
OmniRoute OAuth Provider Architecture
Every provider implements a standard interface with these core components:
config– Provider constants (client IDs, URLs, scopes) imported fromsrc/lib/oauth/constants/oauth.tsflowType– The OAuth grant type (authorization_code_pkce,authorization_code, orimport_token)buildAuthUrl– Constructs the initial authentication URL with required query parametersexchangeToken– Performs the HTTP POST to swap authorization codes for access tokenspostExchange(optional) – Executes additional API calls after token reception (e.g., fetching user metadata)mapTokens– Normalizes raw API responses into OmniRoute’s internal token shape (accessToken,refreshToken,expiresIn,email)
To enable any provider, export the corresponding configuration object and invoke these methods in sequence according to the provider's flowType.
Configuring Claude Code OAuth
The Claude provider (src/lib/oauth/providers/claude.ts) implements the Authorization Code with PKCE flow against Anthropic’s OAuth endpoints.
Claude Configuration Constants
In src/lib/oauth/constants/oauth.ts, the CLAUDE_CONFIG object defines the endpoints and scopes:
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",
};
Implementing the PKCE Flow
- Generate a PKCE code verifier and challenge using
generatePkcePair - Call
claude.buildAuthUrl(config, redirectUri, state, codeChallenge)to create the login URL - After user authentication, extract the
codefrom the redirect URL callback - Exchange the code via
claude.exchangeToken(config, code, redirectUri, codeVerifier, state) - Execute
claude.postExchangeto fetch account bootstrap data fromhttps://api.anthropic.com/api/claude_cli/bootstrap - Normalize the result with
claude.mapTokens, which extracts plan data and generates a randomcliUserID
Claude OAuth 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("Navigate to:", authUrl);
// Capture code from redirect callback
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("Normalized tokens:", omniTokens);
}
Configuring Codex (OpenAI) OAuth
The Codex provider (src/lib/oauth/providers/codex.ts) uses Authorization Code with PKCE, but includes additional query parameters to force fresh authentication and enable multi-account support.
Codex Configuration and Multi-Account Support
The CODEX_CONFIG in src/lib/oauth/constants/oauth.ts specifies:
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 multi-account switching
},
};
Token Exchange Process
The codex.buildAuthUrl method automatically appends extraParams to the authorization URL. Unlike Claude, Codex does not require a postExchange step; the id_token JWT in the initial response contains workspace metadata that codex.mapTokens parses directly.
Codex OAuth 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 are automatically injected
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 for Codex
const omniTokens = codex.mapTokens(rawTokens, {});
console.log(omniTokens);
}
Configuring Cursor OAuth (Import Token Flow)
Cursor uses a unique Import Token flow rather than a web-based OAuth redirect. The provider (src/lib/oauth/providers/cursor.ts) reads authentication tokens directly from the Cursor IDE's local SQLite database.
Accessing Cursor's SQLite Storage
The CURSOR_CONFIG defines platform-specific paths to the state.vscdb file:
- Linux:
~/.config/Cursor/User/globalStorage/state.vscdb - macOS:
~/Library/Application Support/Cursor/User/globalStorage/state.vscdb - Windows:
%APPDATA%\Cursor\User\globalStorage\state.vscdb
The database keys are cursorAuth/accessToken and storage.serviceMachineId.
Token Mapping Implementation
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 pathTemplate = CURSOR_CONFIG.tokenStoragePaths[
platform === "win32" ? "windows" : platform === "darwin" ? "macos" : "linux"
];
return pathTemplate.replace("<user>", os.userInfo().username);
}
function importCursorCredentials() {
const db = sqlite3(getCursorDbPath(), { readonly: true });
const tokenRow = db
.prepare(`SELECT value FROM ItemTable WHERE key = ?`)
.get(CURSOR_CONFIG.dbKeys.accessToken);
const machineRow = db
.prepare(`SELECT value FROM ItemTable WHERE key = ?`)
.get(CURSOR_CONFIG.dbKeys.machineId);
const omniTokens = cursor.mapTokens({
accessToken: tokenRow?.value,
machineId: machineRow?.value,
expiresIn: 86400, // Cursor tokens expire in ~24 hours
});
return omniTokens;
}
Configuring Antigravity OAuth
The Antigravity provider (src/lib/oauth/providers/antigravity.ts) implements the standard Authorization Code flow without PKCE, using Google's OAuth infrastructure.
Google Authorization Code Configuration
The ANTIGRAVITY_CONFIG requires both a client ID and client secret:
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",
"https://www.googleapis.com/auth/experimentsandconfigs",
],
};
Post-Exchange Data Loading
After token exchange, antigravity.postExchange performs three operations:
- Fetches user information from the Google UserInfo endpoint
- Loads Code-Assist configuration via
loadCodeAssistEndpoints - Executes background onboarding (non-blocking)
The mapTokens function then extracts projectId, tier, and email from the aggregated data.
Antigravity OAuth 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 challenge is 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 record:", omniTokens);
}
Using the Generic OAuth API Route
OmniRoute exposes a Next.js API route that abstracts provider implementations:
POST /api/oauth/[provider]/[action]
Supported actions include:
authorize– Returns the authentication URL and PKCE verifierexchange– Swaps the authorization code for tokensimport– Handles import-token flows for Cursor
Example request for Claude authorization:
curl -X POST https://your-app.com/api/oauth/claude/authorize \
-H "Content-Type: application/json" \
-d '{"redirectUri":"https://myapp.local/callback"}'
The route delegates to the specific provider module, ensuring consistent JSON payloads across all four services.
Summary
- Claude Code and Codex use PKCE flows via
src/lib/oauth/providers/claude.tsandsrc/lib/oauth/providers/codex.ts, requiringCLAUDE_OAUTH_CLIENT_IDorCODEX_OAUTH_CLIENT_IDenvironment variables and thegeneratePkcePairutility. - Cursor uses an import-token flow reading from local SQLite storage at
src/lib/oauth/providers/cursor.ts, requiring filesystem access to the Cursor IDE's database. - Antigravity uses a standard authorization-code flow against Google OAuth endpoints via
src/lib/oauth/providers/antigravity.ts, requiring bothANTIGRAVITY_OAUTH_CLIENT_IDandANTIGRAVITY_OAUTH_CLIENT_SECRET. - All providers normalize output through
mapTokensand support the generic/api/oauth/[provider]/[action]endpoint for consistent integration.
Frequently Asked Questions
What environment variables are required to configure OAuth for Claude Code, Codex, Cursor and Antigravity?
For Claude, set CLAUDE_OAUTH_CLIENT_ID and optionally CLAUDE_CODE_REDIRECT_URI. For Codex, set CODEX_OAUTH_CLIENT_ID. For Antigravity, set both ANTIGRAVITY_OAUTH_CLIENT_ID and ANTIGRAVITY_OAUTH_CLIENT_SECRET. Cursor requires no environment variables for OAuth, as it reads tokens directly from the local IDE's SQLite database at ~/.config/Cursor/User/globalStorage/state.vscdb (Linux) or equivalent paths per operating system.
Does OmniRoute use PKCE for all OAuth providers?
No. Claude Code and Codex implement authorization_code_pkce with S256 code challenges. Antigravity uses the standard authorization_code flow where PKCE is optional and not required by the Google OAuth implementation. Cursor does not use a web-based OAuth flow at all; instead, it uses the import_token grant type to read existing credentials from local storage.
How does the Cursor provider handle authentication without a browser redirect?
The Cursor provider (src/lib/oauth/providers/cursor.ts) executes an import-token flow that queries the Cursor IDE's internal SQLite database (state.vscdb). It extracts the cursorAuth/accessToken and storage.serviceMachineId keys using SQL queries, then passes these values to cursor.mapTokens to produce the normalized OmniRoute token format. This approach requires direct filesystem access but eliminates the need for interactive OAuth consent screens.
Can I use the generic API route instead of importing provider modules directly?
Yes. The /api/oauth/[provider]/[action] endpoint delegates to the same underlying functions found in src/lib/oauth/providers/. You can invoke POST /api/oauth/claude/authorize to obtain a login URL, then POST /api/oauth/claude/exchange with the authorization code to complete the flow. This approach is useful for client-side applications or when you want to avoid direct Node.js dependencies on the provider modules.
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 →