[Proposal] Outbound OAuth2 Authentication for AI Gateway #2721
Thenujan-Nagaratnam
started this conversation in
General
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Problem
Today, when the AI Gateway proxies a request to a backend LLM, the only supported outbound authentication mechanism is a static, pre-shared API key (
upstream.auth.type: api-key). A growing number of LLM providers and enterprise-hosted backends — Azure OpenAI behind an Entra ID app registration, self-hosted models behind a corporate OAuth2 proxy, any backend fronted by a standard IdP — require a short-lived OAuth2 bearer token instead of a static key.Solution
Extend the existing typed
upstream.authfield (today:api-keyonly) with a second value,oauth2.At registration time,
gateway-controllervalidates the params and attaches a first-partyoauth2policy (compiled into the runtime image) to the route. At request time the policy:clientId/clientSecretvia HTTP Basic auth (client_secret_basic).Authorization: Bearer <token>and forwards the request.502 Bad Gateway(matches the org's existing auth-failure conventions).Grants supported
client_credentials(RFC 6749 §4.4) — the default, standard machine-to-machine grant.password(RFC 6749 §4.3, Resource Owner Password Credentials)grantTypeis a first-class, forward-compatible parameter specifically so further grants can be added later without a breaking schema change.Token caching (Redis-backed, two-tier)
Tokens are cached in two tiers:
gateway-runtimereplica converges on the same token instead of each independently hitting the IdP, and a replica restart doesn't force a fresh fetch.Redis is an optimization layered on top of the token endpoint, not a hard dependency — controlled by a
failureModesystem parameter:open(default): a Redis outage falls back to fetching directly from the IdP on every request. Auth still works; you just lose cross-replica sharing until Redis recovers.closed: a Redis error is treated as a token-acquisition failure (502).Cache keys are scoped per API (
<keyPrefix><apiId>), not per route/resource:oauth2config lives onupstream.auth.Redis connection settings (
host,port,password,failureMode, timeouts, pool size) are gateway/operator-level system parameters resolved viaconfig.policy_configurations.oauth2_v1.redis.*— not something an individual API publisher sets. This mirrors the existingadvanced-ratelimitpolicy's own Redis configuration exactly.Configuration Reference
User Parameters (API Definition)
Business configuration — including credential material — is set per-API in the API definition (under
upstream.auth), the same asapi-key's existing fields.grantTypeclient_credentialsclient_credentialsorpassword.tokenEndpointclientIdclientSecretclientId.usernamegrantTypeispasswordclient_credentials.passwordgrantTypeispasswordusername. Unused forclient_credentials.scopeSystem Parameters (Gateway/Operator-Level)
Resolved once for the whole gateway via
config.policy_configurations.oauth2_v1.redis.*— not set per API. Every field has a default, so omitting the wholeredisblock is always valid (falls back tolocalhost:6379, no auth).redis.hostlocalhostredis.port6379redis.usernameredis.passwordredis.db0redis.keyPrefixoauth2:token:v1:redis.failureModeopenopenorclosed— see Token Caching above.redis.connectionTimeout5sredis.readTimeout3sredis.writeTimeout3sredis.poolSize0(go-redis default: 10 × GOMAXPROCS)Example: Azure OpenAI via an Entra ID App Registration
Example: Resource Owner Password Credentials (legacy IdP bridging)
Example: Pointing the shared token cache at a specific Redis instance
Set once at the gateway level (not per-API) via
config.tomlor Helm values:Error Responses
All error responses are JSON with
Content-Type: application/json, and never leak the underlying cause.invalid_client,invalid_grant)failed to authenticate request to upstream servicefailed to authenticate request to upstream serviceredis.failureModeisclosedfailed to authenticate request to upstream servicegrantTypeset to an unimplemented value'grantType' must be one of "client_credentials", "password"grantType: passwordset withoutusername/password'username' parameter is required/'password' parameter is requiredOpen Questions
clientAuthMethodcome back as configurable? Do we have to support other methods as well likeclient_secret_post? If so, the hardcoded-Basic decision needs revisiting.upstream.authextension propagate to MCP proxies too?advanced-ratelimit,oauth2) with zero code reuse between them, purely because no shared package exists insdk/core. Worth consolidating, or is per-policy independence intentional?Testing
Fully testable without any dependency on a real IdP or real Redis in production mode:
httptest.Server(token-endpoint stand-in) andminiredis(in-memory Redis).Beta Was this translation helpful? Give feedback.
All reactions