How to Implement Custom Authentication in OmniRoute: A Complete Developer Guide

OmniRoute centralizes all authentication logic in src/sse/services/auth.ts, allowing you to implement custom schemes by extending the extractApiKey() function and optionally adding database-backed validation in src/lib/db/apiKeys.ts.

OmniRoute provides a unified authentication pipeline that handles API keys, OAuth tokens, and custom credentials through a single service layer. Understanding how to implement custom authentication in OmniRoute enables you to integrate proprietary token formats, legacy auth systems, or header-based schemes without modifying downstream executors or route handlers.

Understanding OmniRoute's Authentication Architecture

The Central Service Layer

All authentication flows in OmniRoute converge on the service layer located at src/sse/services/auth.ts. This module exposes extractApiKey() (line 3316), which reads incoming requests and extracts credentials from multiple sources: the standard Authorization header, x-api-key, x-goog-api-key, and optional path-scoped tokens. When you implement custom authentication in OmniRoute, you extend this single function to recognize your proprietary headers or token formats.

The Credential Validation Pipeline

After extraction, the system validates credentials through isValidApiKey() in src/lib/db/apiKeys.ts. This function queries the SQLite-backed api_keys table to verify the key exists and has not expired, returning the associated connectionId. Valid credentials are then materialized into a ProviderCredentials object by getProviderCredentials() (line 1233 in auth.ts), which includes authHeader, authType, and OAuth session data. This object is consumed uniformly by all executors in open-sse/executors/*, API routes like src/sse/handlers/chat.ts, and the WebSocket server at src/server/ws/liveServer.ts.

Implementing a Custom Authentication Scheme

Step 1: Define the Extraction Rule

Create a new helper function in src/sse/services/auth.ts to parse your custom header format. For example, to support a Base64-encoded JSON payload in an x-custom-token header:

// src/sse/services/auth.ts
export function extractCustomToken(request: AuthRequestLike): string | null {
  const hdr = readHeaderValue(request.headers, "x-custom-token");
  if (!hdr) return null;
  try {
    const decoded = Buffer.from(hdr.trim(), "base64").toString("utf8");
    const payload = JSON.parse(decoded);
    return payload?.token ?? null;
  } catch {
    return null;
  }
}

Step 2: Integrate with the Main Flow

Modify the existing extractApiKey() function to include your custom extractor in the fallback chain. Maintain consistency with the existing order: Bearer → x-api-keyx-goog-api-key → custom:

// src/sse/services/auth.ts – inside extractApiKey()
export function extractApiKey(request: AuthRequestLike): string | null {
  // ... existing built-in checks for Authorization, x-api-key, etc.
  
  const custom = extractCustomToken(request);
  if (custom) {
    return custom;  // custom scheme wins
  }
  
  return null;
}

Because clientApiRouteAuth in src/shared/utils/clientApiRouteAuth.ts and the policy guard in src/server/authz/policies/clientApi.ts both call extractApiKey(), your new scheme automatically protects all API routes without additional configuration.

Step 3: Persist Custom Tokens (Optional)

If your custom tokens require database persistence, extend src/lib/db/apiKeys.ts to support a new table. Add a migration creating a custom_tokens table, then implement the lookup function:

// src/lib/db/apiKeys.ts
export async function getCustomTokenInfo(token: string) {
  const db = getDbInstance();
  return db.get<{ connectionId: string }>(
    `SELECT connectionId FROM custom_tokens WHERE token = ?`, 
    token
  );
}

Step 4: Validate Against the Database

Update the validation logic to branch for custom tokens. You can either extend isValidApiKey() or create a parallel isValidCustomToken() function that checks both the standard api_keys table and your new custom_tokens table:

// src/sse/services/auth.ts
export async function isValidApiKey(key: string) {
  // Check standard API keys first
  const standard = await lookupStandardKey(key);
  if (standard) return standard;
  
  // Fallback to custom token validation
  return await getCustomTokenInfo(key);
}

Testing Your Custom Implementation

cURL Requests with Custom Headers

Test your implementation by sending a Base64-encoded JSON payload:

curl -H "x-custom-token: $(echo -n '{"token":"abc123"}' | base64)" \
     http://localhost:20128/v1/chat/completions

JavaScript Client Implementation

For browser or Node.js clients, encode the payload before sending:

const payload = JSON.stringify({ token: "abc123" });
const encoded = btoa(payload);

await fetch("http://localhost:20128/v1/chat/completions", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-custom-token": encoded,
  },
  body: JSON.stringify({ model: "gpt-4", messages: [...] })
});

Server-Side Verification

When building custom handlers, use the same authentication functions to verify requests:

import { extractApiKey, isValidApiKey } from "@/sse/services/auth";

export async function handler(request: Request) {
  const rawKey = extractApiKey(request);
  if (!rawKey) return new Response("Unauthorized", { status: 401 });

  const cred = await isValidApiKey(rawKey);
  if (!cred) return new Response("Invalid token", { status: 403 });

  // cred.connectionId can now be passed to the executor
  return new Response(JSON.stringify({ connectionId: cred.connectionId }));
}

Key Files and Extension Points

Understanding these core files ensures your custom authentication integrates seamlessly with OmniRoute's security model:

Summary

  • OmniRoute uses a single service layer (src/sse/services/auth.ts) for all authentication, making custom schemes manageable by modifying one file.

  • Extend extractApiKey() to recognize custom headers or token formats, placing your logic after built-in checks for Authorization, x-api-key, and x-goog-api-key.

  • Validate custom tokens by extending src/lib/db/apiKeys.ts with new tables and lookup functions, then integrate these into isValidApiKey() or parallel validation functions.

  • Automatic propagation occurs because all API routes, WebSocket handlers, and SDKs consume the same extractApiKey() function and ProviderCredentials object.

  • Zero downstream changes are required; executors in open-sse/executors/* receive the normalized credential object regardless of the extraction method.

Frequently Asked Questions

Where is the authentication logic centralized in OmniRoute?

All authentication logic is centralized in src/sse/services/auth.ts. This file contains extractApiKey() for credential extraction and getProviderCredentials() for materializing the ProviderCredentials object that downstream components consume. According to the OmniRoute source code, both API routes and WebSocket connections import from this single module, ensuring consistent authentication behavior across the entire application.

Can I use custom authentication with WebSocket connections?

Yes. The WebSocket server at src/server/ws/liveServer.ts loads the authentication module via loadAuthModule() and extracts keys during the initial handshake using the same extractApiKey() function. Because WebSocket authentication reuses the service layer in src/sse/services/auth.ts, any custom extractor you add becomes immediately available to WebSocket clients without modifying the live server code.

How do I store custom tokens in the database?

Add a migration to create a new table (e.g., custom_tokens) in the SQLite database, then implement a lookup function in src/lib/db/apiKeys.ts. Reference this function from your validation logic in src/sse/services/auth.ts. The existing api_keys table schema provides a template for implementing expiration checks and connection ID mappings for your custom tokens.

Is it possible to disable built-in authentication headers?

While you can modify extractApiKey() to skip built-in checks, OmniRoute does not provide a configuration flag to disable standard headers like Authorization or x-api-key. To prioritize your custom scheme, place your extraction logic at the beginning of the function's fallback chain. If you require conditional enabling based on settings, add a configuration entry in src/lib/resilience/settings.ts and check this setting inside extractApiKey() before processing standard headers.

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 →