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:
- Add the service under
src/lib/oauth/services/myprovider.ts - Export it from
src/lib/oauth/providers/index.ts - 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
modelsUrlto a JSON file in your repository - Dynamic fetch: Point
modelsUrlto 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:
resolvePublicCredis 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
resolvePublicCredfrom@omniroute/open-sse/utils/publicCreds.tsfor all OAuth credentials—never hard-code secrets - Place provider definitions in
src/lib/oauth/providers/and export aCONFIGobject - Register providers in
open-sse/config/providerRegistry.tswith 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →