Microsoft Entra ID Front-Door Auth¶
Relayna Gateway releases 0.1.7 and later can put Microsoft Entra ID in front of provider
traffic while keeping Relayna virtual keys as the policy, budget, rate-limit,
guardrail, and usage anchor.
The feature is opt-in. With ENTRA_AUTH_ENABLED=false, which is the default,
existing clients keep using:
Authorization: Bearer rk_live_...
With ENTRA_AUTH_ENABLED=true, provider traffic must use a two-credential
contract:
Authorization: Bearer <Entra access token>
X-Relayna-Key: rk_live_...
X-Relayna-Key is the default Relayna key header. Operators can change it with
ENTRA_RELAYNA_KEY_HEADER. Earlier review builds used X-AIH-API-Key; Gateway
still strips X-AIH-API-Key before upstream forwarding as a legacy sensitive
header, but the documented default is now X-Relayna-Key.
Scope¶
Entra front-door auth applies to provider/proxy traffic handled by the Pingora proxy plane:
POST /v1/chat/completionsPOST /v1/responsesPOST /providers/openai/*- Built-in internal service routes such as
/summary,/translation,/ocr, and/embeddings - Registered service wildcard routes under
/services/<service-name>/*
The control plane remains protected by operator tokens on /admin-ui/*.
Entra front-door auth does not replace GATEWAY_ADMIN_TOKEN or scoped operator
tokens.
Why Two Credentials¶
Entra and Relayna keys answer different questions.
| Layer | Credential | Purpose |
|---|---|---|
| Enterprise front door | Entra access token | Proves tenant identity, application identity, user identity, scope, role, and group entitlement. |
| Relayna control plane | Relayna virtual key | Selects project, owner, policy, route permissions, model permissions, rate limits, budgets, guardrails, and usage attribution. |
This lets enterprise identity teams manage tenant-level access without giving up Relayna's per-project metering and policy controls.
Request Flow¶
sequenceDiagram
participant Client
participant Gateway as Relayna Gateway
participant Entra as Entra OIDC/JWKS
participant Store as PostgreSQL/Redis
participant Upstream as LiteLLM/Provider/Service
Client->>Gateway: Authorization: Bearer <Entra JWT>
Client->>Gateway: X-Relayna-Key: rk_live_...
Gateway->>Gateway: Resolve proxy route
Gateway->>Entra: Fetch OIDC metadata and JWKS on cache miss
Gateway->>Gateway: Verify kid, alg, signature, issuer, audience, timestamps, tenant, version
Gateway->>Gateway: Check required scope, role, and groups
Gateway->>Store: Authenticate Relayna virtual key
Gateway->>Store: Enforce policy, rate limit, budget, guardrails
Gateway->>Gateway: Strip client credentials and identity headers
Gateway->>Upstream: Forward with internal provider credential
Gateway->>Store: Record usage and attribution
Gateway-->>Client: Upstream response or stable Gateway error
Configuration¶
Front-door auth can be configured from the Admin portal Settings page or from deployment environment variables. Admin-saved settings are useful for operator changes after deployment; environment variables remain useful for bootstrap, GitOps, and immutable deployments. See Admin Portal for the field-by-field UI walkthrough and screenshots.
Release 0.1.26 uses the same Entra application registration for confidential
portal sign-in and API authorization. Configure that registration with a Web
platform, identifier URI api://<application-id>, requested access-token
version 2, and the gateway.invoke and gateway.monitor.read application
roles. Request-plane managed identities receive only gateway.invoke; service
monitoring identities receive only gateway.monitor.read.
The environment variables are listed below. Empty strings are treated as unset.
| Variable | Required | Default | Description |
|---|---|---|---|
ENTRA_APPLICATION_ID |
When any Entra mode is enabled | none | One Entra application ID GUID shared by portal OIDC, request-plane access-token validation, and owner monitoring. For v2 access tokens this is the expected aud; managed identities request api://<application-id>/.default. |
ENTRA_AUTH_ENABLED |
No | false |
Enables direct Entra JWT validation for proxy traffic. |
ENTRA_TENANT_ID |
When enabled | none | Expected tid claim. Use the tenant GUID or tenant identifier your app tokens carry. |
ENTRA_ISSUER |
When enabled | none | Expected token issuer. For v2 tokens this is usually https://login.microsoftonline.com/<tenant-id>/v2.0. |
ENTRA_OIDC_DISCOVERY_URL |
When enabled | none | OIDC metadata URL that returns issuer and jwks_uri. |
ENTRA_REQUIRED_SCOPE |
No | none | Optional required delegated scope from the scp claim. |
ENTRA_REQUIRED_ROLE |
No | gateway.invoke |
Required app role from the roles claim for request-plane Entra and trusted Apigee identities. Override only when the shared application defines a reviewed equivalent. |
ENTRA_ALLOWED_GROUPS |
No | none | Optional comma-separated group IDs. At least one must appear in groups. |
ENTRA_ACCEPTED_ALGORITHMS |
No | RS256 |
Comma-separated accepted JWT algorithms. Gateway currently validates RSA JWKS keys. |
ENTRA_RELAYNA_KEY_HEADER |
No | X-Relayna-Key |
Header that carries the Relayna rk_live_... key in Entra and Apigee modes. |
ENTRA_JWKS_CACHE_TTL_SECONDS |
No | 300 |
JWKS cache lifetime. Unknown kid triggers a refresh before failing. |
ENTRA_CLOCK_SKEW_SECONDS |
No | 60 |
Allowed clock skew for exp, nbf, and iat. |
Minimal direct Entra configuration:
export ENTRA_AUTH_ENABLED="true"
export ENTRA_APPLICATION_ID="11111111-1111-1111-1111-111111111111"
export ENTRA_TENANT_ID="00000000-0000-0000-0000-000000000000"
export ENTRA_ISSUER="https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0"
export ENTRA_OIDC_DISCOVERY_URL="https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0/.well-known/openid-configuration"
export ENTRA_REQUIRED_ROLE="gateway.invoke"
export ENTRA_RELAYNA_KEY_HEADER="X-Relayna-Key"
Managed identities request the shared API resource and receive the application roles assigned to their service principal:
export RELAYNA_GATEWAY_SCOPE="api://${ENTRA_APPLICATION_ID}/.default"
Request-plane identities receive gateway.invoke. Service-monitoring
identities receive gateway.monitor.read. Do not assign both unless the
combined capability boundary is intentional.
Group allowlist configuration:
export ENTRA_AUTH_ENABLED="true"
export ENTRA_ALLOWED_GROUPS="11111111-1111-1111-1111-111111111111,22222222-2222-2222-2222-222222222222"
Client Contract¶
When Entra mode is enabled, a successful proxy request needs both credentials:
curl -sS http://127.0.0.1:8080/v1/chat/completions \
-H "Authorization: Bearer $ENTRA_ACCESS_TOKEN" \
-H "X-Relayna-Key: $RELAYNA_VIRTUAL_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o-mini",
"messages": [{"role": "user", "content": "Say hello"}]
}'
If ENTRA_RELAYNA_KEY_HEADER is set to another valid HTTP header name, clients
must use that header instead:
export ENTRA_RELAYNA_KEY_HEADER="X-Company-Relayna-Key"
curl -sS http://127.0.0.1:8080/v1/responses \
-H "Authorization: Bearer $ENTRA_ACCESS_TOKEN" \
-H "X-Company-Relayna-Key: $RELAYNA_VIRTUAL_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini","input":"Summarize this request"}'
Do not send the Relayna key in Authorization while Entra mode is enabled.
Authorization is reserved for the Entra JWT in this mode.
Validation Behavior¶
Gateway validates the token and claims before it looks up the Relayna virtual key. This prevents invalid enterprise identities from consuming virtual-key, policy, rate-limit, or budget resources.
Direct JWT validation checks:
Authorizationexists and uses theBearerscheme.- JWT header is parseable.
kidexists.algis inENTRA_ACCEPTED_ALGORITHMS.- OIDC metadata fetch succeeds.
- OIDC metadata
issuerequalsENTRA_ISSUER. - JWKS fetch succeeds and contains a matching
kid. - JWKS key type is RSA.
- JWKS key
alg, when present, matches the JWT header algorithm. - Signature validates against JWKS modulus and exponent.
issequalsENTRA_ISSUER.tidequalsENTRA_TENANT_ID.audcontainsENTRA_APPLICATION_ID.expis not expired after allowed clock skew.nbf, when present, is not in the future after allowed clock skew.iat, when present, is not in the future after allowed clock skew.veris1.0or2.0.- Group overage claims fail closed.
scpcontainsENTRA_REQUIRED_SCOPEwhen configured.rolescontainsENTRA_REQUIRED_ROLEwhen configured.groupsintersectsENTRA_ALLOWED_GROUPSwhen configured.
The sanitized identity context can include tenant ID, subject, object ID, app ID, authorized party, scopes, roles, groups, token version, and source. Gateway does not retain or forward the raw Entra JWT.
Stable Error Codes¶
Entra failures use stable Gateway error codes:
| Error code | Typical cause |
|---|---|
missing_entra_authorization |
Entra mode is enabled but no Authorization header is present. |
malformed_entra_authorization |
Header is not Bearer <token> or token header cannot be parsed. |
invalid_entra_token |
Unknown kid, invalid signature, unsupported algorithm, invalid JWKS, invalid nbf/iat, unsupported token version, or malformed token body. |
expired_entra_token |
exp is expired after clock skew. |
invalid_entra_audience |
aud does not contain ENTRA_APPLICATION_ID. |
invalid_entra_issuer |
iss, tid, or OIDC metadata issuer does not match config. |
insufficient_entra_authorization |
Missing required scope, role, or group, or token uses group overage. |
missing_authorization |
Entra passed, but the configured Relayna key header is missing. |
invalid_virtual_key, disabled_virtual_key, revoked_virtual_key, expired_virtual_key |
Entra passed, but Relayna key validation failed under existing rules. |
Header Stripping¶
Gateway strips client credentials before forwarding to LiteLLM, direct providers, and registered services:
Authorization- The configured
ENTRA_RELAYNA_KEY_HEADER X-Relayna-KeyX-AIH-API-KeyProxy-AuthorizationX-Apigee-Entra-IdentityX-Apigee-Entra-Signature
Provider credentials are injected only from Gateway-owned configuration or service registration data.
Route Behavior¶
The auth contract is identical across proxy routes. The route resolver still decides policy, upstream type, body limits, and timeouts after Entra succeeds.
| Route family | Entra behavior |
|---|---|
/v1/chat/completions |
Entra first, Relayna key second, then existing OpenAI-compatible policy and LiteLLM/direct routing. |
/v1/responses |
Same two-step auth and credential stripping. |
/providers/openai/* |
Same auth, then direct OpenAI-compatible upstream credential injection. |
| Built-in internal routes | Same auth, then configured internal service credential injection. |
/services/<service-name>/* |
Same auth, then service registry lookup, service policy, and registered service upstream credential injection. |
Kubernetes Rollout¶
- Deploy the current Gateway release with Entra disabled first.
- Confirm the existing
Authorization: Bearer rk_live_...path still works. - Add Entra config to your secret manager or deployment environment.
- Enable
ENTRA_AUTH_ENABLED=trueon one staging replica. - Send requests with
Authorization: Bearer <Entra JWT>andX-Relayna-Key: rk_live_.... - Confirm invalid JWTs fail before virtual-key validation.
- Confirm upstream services never receive Entra JWTs or Relayna keys.
- Roll out to production after rate-limit, budget, policy, and usage attribution match the virtual-key-only baseline.
The example Kubernetes manifest keeps Entra disabled by default and includes empty placeholders for required Entra values.
Verification Commands¶
Useful local checks after changing Entra configuration or code:
python3 scripts/validate-release-metadata.py v0.1.26
cargo test -p gateway-core entra::tests --all-features
cargo test -p gateway-proxy relayna_key_header_is_available_for_apigee_only_mode --all-features
cargo test --workspace --all-features
For a full real-environment review with Postgres, Redis, Gateway, mock OIDC, mock Apigee, and mock upstreams:
internal/test-reports/entra-front-door-real-env/run.sh
The harness verifies success and failure cases for direct Entra JWT validation,
configured Relayna key headers, /v1/chat/completions, /v1/responses,
/providers/openai/*, built-in internal service routes, /services/*, Apigee
JWT revalidation, trusted Apigee HMAC identity proof, header stripping, and
usage attribution.