How to Implement Custom OAuth Providers with OmniRoute's Public Credential Embedding

Implement custom OAuth providers in OmniRoute by creating a provider module in src/lib/oauth/providers/ that uses resolvePublicCred from @omniroute/open-sse/utils/publicCreds.ts, then register it in open-sse/config/providerRegistry.ts.

OmniRoute centralizes every LLM and provider interaction through a provider registry, making it straightforward to add new OAuth-based providers while keeping credentials secure. This guide walks through the complete implementation using OmniRoute's public credential embedding system, which satisfies Hard Rule #11—no literal secrets in source code.

Creating a New OAuth Provider Module

Every custom OAuth provider starts with a dedicated file under src/lib/oauth/providers/. The key requirement is using the resolvePublicCred helper to handle credentials safely.

The resolvePublicCred Pattern

In open-sse/utils/publicCreds.ts, the resolvePublicCred function reads a default credential bundled in the repository while allowing an environment variable to override it at runtime. This is the core mechanism for public credential embedding.

// src/lib/oauth/providers/my-provider.ts
import { resolvePublicCred } from "@omniroute/open-sse/utils/publicCreds.ts";

export const CONFIG = {
  // Resolves from embedded default OR environment variable MYPROV_OAUTH_CLIENT_ID
  clientIdDefault: resolvePublicCred("myprov_id", "MYPROV_OAUTH_CLIENT_ID"),
  // Resolves from embedded default OR environment variable MYPROV_OAUTH_CLIENT_SECRET
  clientSecretDefault: resolvePublicCred("myprov_secret", "MYPROV_OAUTH_CLIENT_SECRET"),
  tokenUrl: "https://auth.myprovider.com/oauth/token",
  authUrl: "https://auth.myprovider.com/oauth/authorize",
  scopes: ["openid", "email", "profile"],
};

The first argument ("myprov_id") references an embedded default value. The second argument ("MYPROV_OAUTH_CLIENT_ID") names the environment variable that can override it.

Registering Your Provider in the Registry

After creating the provider module, expose it through open-sse/config/providerRegistry.ts. The registry ties together the provider's type, executor, and supported models.

// open-sse/config/providerRegistry.ts (excerpt)
import { CONFIG as MYPROV_CONFIG } from "@/lib/oauth/providers/my-provider.ts";

export const REGISTRY = {
  // …existing providers
  myprovider: {
    type: "oauth",
    config: MYPROV_CONFIG,
    // Models fetched lazily—static list or remote JSON
    modelsUrl: "https://api.myprovider.com/v1/models",
    // Reuse "default" executor for OpenAI-compatible APIs, or create custom
    executor: "default",
  },
} as const;

The type must be "oauth" for all OAuth-based providers. The executor field typically uses "default" for providers with standard OpenAI-compatible API surfaces—only specify a custom executor for non-standard implementations.

Handling Non-Standard OAuth Flows

Most providers reuse the generic OAuth flow located in src/lib/oauth/services/default.ts. If your provider has quirks (non-standard token response, custom refresh logic, or unusual scope handling), create a service file implementing the OAuthService interface:

  1. Add the service under src/lib/oauth/services/myprovider.ts
  2. Export it from src/lib/oauth/providers/index.ts
  3. Reference it in your registry entry with executor: "myprovider"

For the majority of providers, the default flow works unchanged and requires no additional service code.

Configuring Model Discovery

OmniRoute's auto-combo router automatically discovers and exposes models on the /v1/models endpoint. You have two options for model configuration:

  • Static list: Point modelsUrl to a JSON file in your repository
  • Dynamic fetch: Point modelsUrl to your provider's remote model endpoint

If your provider doesn't support model listing, omit modelsUrl and the system will use fallback behavior.

Complete Working Example

Here's a full implementation for a hypothetical "ExampleProvider":

// src/lib/oauth/providers/example.ts
import { resolvePublicCred } from "@omniroute/open-sse/utils/publicCreds.ts";

export const CONFIG = {
  clientIdDefault: resolvePublicCred("example_id", "EXAMPLE_OAUTH_CLIENT_ID"),
  clientSecretDefault: resolvePublicCred("example_secret", "EXAMPLE_OAUTH_CLIENT_SECRET"),
  authUrl: "https://login.example.com/oauth/authorize",
  tokenUrl: "https://login.example.com/oauth/token",
  scopes: ["openid", "email"],
};
// open-sse/config/providerRegistry.ts
import { CONFIG as EXAMPLE_CONFIG } from "@/lib/oauth/providers/example.ts";

export const REGISTRY = {
  example: {
    type: "oauth",
    config: EXAMPLE_CONFIG,
    modelsUrl: "https://api.example.com/v1/models",
    executor: "default",
  },
} as const;

Clients select your custom provider using the provider field in API requests:

const response = await fetch("/v1/chat/completions", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    model: "example-gpt-4",
    messages: [{ role: "user", content: "Hello" }],
    provider: "example",
  }),
});

Required Unit Tests

Every new OAuth provider must include tests under tests/unit/ that verify:

  • resolvePublicCred is used (no hard-coded literals)
  • The provider entry exists in REGISTRY
  • Model list loads correctly or falls back gracefully
// tests/unit/myprovider.test.ts
import { REGISTRY } from "../../open-sse/config/providerRegistry.ts";

test("myprovider is registered", () => {
  const entry = REGISTRY.myprovider;
  assert.ok(entry, "myprovider must be defined");
});

test("clientId resolves via resolvePublicCred", async () => {
  const { resolvePublicCred } = await import("@omniroute/open-sse/utils/publicCreds.ts");
  const id = resolvePublicCred("myprov_id", "MYPROV_OAUTH_CLIENT_ID");
  assert.match(id, /^[a-z0-9_-]+$/i);
});

Key Files Reference

File Purpose
open-sse/utils/publicCreds.ts Core helper for credential resolution with environment overrides
open-sse/config/providerRegistry.ts Central registry mapping provider keys to configuration
src/lib/oauth/services/default.ts Generic OAuth flow implementation
src/lib/oauth/providers/ Directory for new provider definitions

Summary

  • Use resolvePublicCred from @omniroute/open-sse/utils/publicCreds.ts for all OAuth credentials—never hard-code secrets
  • Place provider definitions in src/lib/oauth/providers/ and export a CONFIG object
  • Register providers in open-sse/config/providerRegistry.ts with type "oauth", proper config reference, and executor selection
  • Reuse the default executor for OpenAI-compatible APIs; create custom services only for non-standard behaviors
  • Write unit tests verifying credential resolution and registry registration

Frequently Asked Questions

What is public credential embedding in OmniRoute?

Public credential embedding is OmniRoute's security pattern where default OAuth credentials are bundled in the repository but can be overridden via environment variables at runtime. The resolvePublicCred function in open-sse/utils/publicCreds.ts implements this pattern, ensuring no literal secrets appear in source code while maintaining deploy-time flexibility.

When do I need a custom OAuth executor versus using "default"?

Use executor: "default" when your provider follows standard OAuth 2.0 flows and OpenAI-compatible API responses. Create a custom executor in src/lib/oauth/services/ only when your provider has non-standard behaviors—such as custom token response formats, unusual refresh token handling, or proprietary authentication extensions.

How does resolvePublicCred prevent secrets from leaking?

resolvePublicCred takes two arguments: a key for an embedded default value and an environment variable name. The embedded default can be a placeholder or public value, while the actual secret is injected at runtime via environment variables. This satisfies OmniRoute Hard Rule #11 by ensuring production secrets never appear in git history or source code.

Can I use environment variables exclusively without embedded defaults?

Yes. While the pattern shows embedded defaults, you can structure your resolvePublicCred calls so that the embedded value is a non-functional placeholder (empty string or "REPLACE_ME"), forcing runtime configuration through environment variables only. The function will always prefer the environment variable when present.

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 →