# Authenticate gateway requests with an external IdP

> What authenticates a request to a data plane at 0.7.0: an API key issued for the project, the caller's own provider credential on the passthrough path, or, in preview, a token from the organization's own identity provider verified against a gateway auth policy or a project's OIDC contract.

Enterprise Tier

A request to a <Term id="data-plane">data plane</Term> authenticates with an API key issued for the project, or carries the caller's own provider credential on the passthrough path. From 0.7.0 an application can instead present a token minted by the organization's own <Term id="identity-provider-idp">identity provider</Term>, verified at the gateway against a set of registered issuers. That token path is a preview: it works end to end, budgets and rate limits do not yet apply to it. A <Link to="/agent-router-enterprise/guides/operate-and-govern/project-and-data-plane-management/authenticate-with-an-idp-token/">worked example</Link> walks one provider through it end to end.

:::info[Preview: authenticating with an identity-provider token]
The token path shipped in two steps. 0.6.0 stores gateway auth policies and verifies a token sent beside an API key. 0.7.0 serves a call made with a token alone, provided every data plane serving the project is on 0.7.0 or later. Budgets and rate limits are recorded per API key and do not yet apply to a caller admitted by a token, so cap that usage at the identity provider until they do. A worked configuration for one provider, with the token at each stage, is in [Worked example of IdP token authentication](/agent-router-enterprise/guides/operate-and-govern/project-and-data-plane-management/authenticate-with-an-idp-token/). The management API reference linked below is the complete field list.
:::

## How it works

Three things are configured, and one is created automatically:

| Item | Where it lives | Set by |
| :---- | :---- | :---- |
| **Trusted issuer** | Agent Router, once per identity provider | Platform operator |
| **Claim value** on a project | Agent Router, once per project | Platform operator |
| **Access and project scopes** in the token | The identity provider, per application | Identity team |
| **External application** | Agent Router, one per application per project | Created on the application's first valid call |

On every request, the gateway:

1. Checks the token's signature against the trusted issuer's published keys, and checks its issuer, audience, and expiry.
1. Reads the access claim. The token must carry exactly one, for this environment.
1. Reads the project claim. The token must carry exactly one, and its value must be set on a project served on the hostname the request arrived on. There is no default project.
1. Finds the external application for the issuer, the token's client ID, and that project, or creates it on the first call.
1. Handles the request with the project's models and guardrails and the application's budget and rate limit, and records usage against the application.

The token is never forwarded to a model provider. Identity headers sent by the caller are removed before the request is handled, so a caller cannot claim to be another application. Other token fields, such as `sub`, `iat`, and `jti`, do not decide whether a call is accepted.

## What authenticates a request at 0.7.0

A request reaches a project through a hostname the data plane declares, and every project the data plane carries a <Term id="project-label">project label</Term> for is served on every one of its hostnames. Nothing about the hostname selects the project: the credential does. Three credentials exist.

- **An API key issued for the project**, on the `Authorization` header. The key names its project, so the request needs no other identifier, and a key from a project the data plane does not serve is refused with `403`. Keys are issued by developers in the Console and governed organization-wide by operators; issuing, auditing, and revoking them is covered in [Onboard developers and issue keys](/agent-router-enterprise/guides/operate-and-govern/onboard-developers-and-issue-keys/) and [Issue a project API key](/agent-router-enterprise/guides/operate-and-govern/project-and-data-plane-management/issue-a-project-api-key/).
- **The caller's own provider credential**, on the passthrough path: the request carries a credential for the upstream provider itself (an Anthropic subscription token, for example), and the gateway forwards rather than substitutes it. The behavior and its routing consequences are covered in [Use your own provider credentials](/agent-router-service/guides/use-your-own-provider-credentials/) and, for the operator-side setup, [Enable passthrough for Claude Code](/agent-router-enterprise/guides/operate-and-govern/manage-models-and-providers/enable-passthrough-for-claude-code/).
- **A token from the organization's own identity provider**, in preview from 0.7.0, sent in the standard `Authorization: Bearer` header together with a header naming the project. The rest of this page describes it.

Single sign-on is a different surface: [SSO](/agent-router-enterprise/guides/identity-and-access-management/single-sign-on/sso-configuration-guide/) authenticates **people into the consoles**, and its identities can then issue API keys. It never authenticates a request to a data plane.

## How a token call works

An API key names exactly one project. A token names an application, and an application may be entitled to several projects, so a token call carries one extra header naming the project the request runs as. The data plane takes that header as a candidate, never as a decision: the configuration the project publishes decides whether this token may use it.

Two configurations admit a token, and they are shaped for different situations.

| Configuration | Where it is written | Who it suits |
| :---- | :---- | :---- |
| A **gateway auth policy**, with identity mapping rules, on a <Term id="gateway">gateway</Term> | The [`GatewayAuthPolicyService`](/reference/api/gateway-auth-policies/agentrouter-admin-v-1-gateway-auth-policy-service/) of the management API, one policy per trusted issuer, up to four enabled per gateway | An operator registering a set of issuers once for every project served on that gateway, with per-issuer key sources, audiences, and required scopes |
| A **project OIDC contract**, on one project | [UpsertProjectOIDCConfig](/reference/api/admin/agentrouter-admin-v-1-admin-service-upsert-project-oidc-config/), one contract per project | A project team naming the one issuer, audience, scope, and optional entitlement its own tokens carry |

On either path the data plane verifies the token's signature against the issuer's key set, its expiry, and its audience, then checks what the configuration requires of its claims, and only then resolves the project. A token from an issuer the gateway does not register is refused rather than tried against another issuer's keys.

### The set of trusted issuers

One gateway carries several trusted identity providers, each with its own key source and its own accepted audiences. Every policy names its issuer, and one gateway takes at most one enabled policy per issuer, so the gateway selects the verifier from the issuer the token names and nothing else. The policy fields:

| Field | What it does |
| :---- | :---- |
| `issuer` | The value the token's `iss` claim must carry. Required, and unique among the gateway's enabled policies. |
| `jwks_uri` or `jwks_inline` | Where the signing keys come from. A `jwks_uri` must name the provider by a public hostname, carry no user name or password, and not resolve to an internal address. An inline key set holds at most 32 keys and 16 KiB; serve a larger set from a `jwks_uri`. |
| `audiences` | The audiences a token may carry, up to eight. |
| `required_scopes` | Scopes the token must carry, every one of them, in its `scope` or `scp` claim, matched case-sensitively. A token missing one gets `403`. Updates replace the list, so omitting it removes the requirement. |
| `extraction_header` | The header the token is read from when it is not sent as a Bearer value. Defaults to `x-tars-idp-token`. `Authorization` is refused, as is any header the gateway cannot remove after verification, such as `x-api-key` or `cookie`. |
| `environment_claim` | The claim carrying the environment the token was issued for, compared against the environment the gateway declares. An enabled policy naming one is refused while the gateway declares no environment, and the gateway refuses a token whose claim does not name its environment. |

Identity mapping rules, under the same service, say which claim identifies the application and whether an application unseen before is given an identity on its first call (`jit`, bounded by a per-project cap) or must be pre-registered. A `jit` rule names the project the application is created in, and that project has to be attached to the policy's gateway before the rule is written.

### Which credentials a project accepts

Each project's membership of a gateway records `accepted_credentials`, written with [UpdateProjectGatewayProject](/reference/api/admin/agentrouter-admin-v-1-admin-service-update-project-gateway-project/). A new attachment starts at `ACCEPTED_CREDENTIALS_API_KEY`, which is what every project did before the field existed. `ACCEPTED_CREDENTIALS_BOTH` admits either credential, and `ACCEPTED_CREDENTIALS_TOKEN` makes the data plane refuse API keys for that project. Both are refused while the project holds no enabled policy on that gateway, and the project's last enabled policy cannot be disabled or deleted while the membership declares either one. A data plane older than 0.7.0 authenticates API keys alone, whatever the membership declares.

### What an application sends

The same base URL, endpoints, and model names as an API-key caller, plus the project header:

```bash
curl https://gw.example.com/v1/chat/completions \
  -H "Authorization: Bearer $IDP_TOKEN" \
  -H "x-tars-project: payments-assist" \
  -d '{"model": "claude-sonnet-4-5", "messages": [{"role": "user", "content": "Hello"}]}'
```

The header is `x-tars-project` on the gateway auth policy path and `X-TARS-PROJECT-ID` on the project OIDC contract path; both take the project's name. The data plane removes either header, and the token, before anything is forwarded to a model provider or an MCP server. A request that carries both a token and an `x-api-key` header is refused as a credentials conflict rather than guessed at.

On the gateway auth policy path, the first call from an application creates the application's own API key when a mapping rule allows it, and later calls reuse it. Request logs and usage are attributed to that key, which is how a token call appears in the Console today.

## What a refusal means

Each refusal carries its own reason in the error body, and the data plane records the reason in its metrics. The ones a caller is most likely to meet:

| Status | Reason | Meaning |
| :---- | :---- | :---- |
| `400` | `missing_project` or `project_header_required` | The request carries a token and no project header. |
| `400` | `credentials_conflict` | The request carries both a Bearer token and an `x-api-key` header. |
| `401` | `unregistered_issuer` | The token's `iss` names an issuer this gateway does not register. |
| `401` | `bad_signature`, `expired`, `wrong_audience` | The token did not verify against the issuer's keys, has expired, or was not issued for an audience the gateway accepts. |
| `403` | `project_not_served` | The gateway does not serve the project the header names. |
| `403` | `token_not_accepted` | The project named accepts API keys only on this gateway. |
| `403` | `issuer_not_accepted` | The issuer is registered on the gateway, but the project named does not accept tokens from it. |
| `403` | `missing_scope` or `insufficient_scope` | The token lacks a scope the policy or contract requires. |
| `403` | `environment_mismatch`, `environment_claim_missing`, `environment_undeclared` | The token's environment claim does not name the gateway's environment, the token carries no environment claim, or the gateway declares no environment. |
| `503` | `keys_unavailable` | The gateway does not hold the issuer's keys right now. Retry. |

A `403` on the project contract path that names nothing is deliberate: a project a token has no relationship with confirms neither that the project exists nor whose token this is. A refusal on the project OIDC contract path carries a `reference_id` to quote to support. A refusal on the gateway auth policy path carries its reason in `code` and no `reference_id`; the `X-Request-ID` response header identifies it.

## What is refused when a policy is written

The management API refuses a gateway auth policy write, with the reason in the response, when:

- `jwks_uri` carries a user name or password, writes an IPv4 address as anything other than a dotted quad, or names or resolves to a carrier-grade NAT, multicast, reserved, or other internal address;
- `jwks_inline` holds more than 32 keys or more than 16 KiB;
- `extraction_header` is one the gateway cannot remove after verifying the token, which rules out the identity headers the gateway sets and credential headers such as `x-api-key`, `cookie`, and `proxy-authorization`;
- an identity mapping rule names a `jit_project_id` that is not a project of the policy's organization or is not attached to the policy's gateway, sets `jit_principal_cap` on a `pre_registered` rule, or sets a `principal_type` other than `external` on a `jit` rule;
- the gateway already carries four enabled policies, or another enabled policy names the same issuer.

An unrelated update does not recheck an unchanged stored value, so an existing policy keeps working. An enabled policy that still reads its token from a header the gateway cannot strip produces the warning `gateway auth policy reads its token from a header the gateway cannot keep for verification and remove before the upstream` in the data plane and management plane logs; move each policy it names to `x-tars-idp-token` or another dedicated header.

The project OIDC contract has one refusal of its own: the management plane reads the issuer's discovery document when the contract is written, and refuses an issuer on a loopback, link-local, private, carrier-grade NAT, unique-local, or otherwise non-public address. An identity provider inside the organization's own network, such as a Keycloak install, needs `OIDC_DISCOVERY_ALLOW_PRIVATE_ADDRESSES=true` on the management plane before its contract is written.

## Upgrading a data plane that carries a policy

Upgrade the management plane before the data planes. From 0.7.0 the data plane removes `x-tars-idp-token` and every other identity header it sets from the requests a caller sends, and a data plane upgraded ahead of its management plane removes that header before the gateway can verify it on a gateway with an existing policy. The [data plane upgrade guide](/agent-router-enterprise/self-hosted-data-plane/upgrade-the-data-plane/) covers the order; the headers the data plane strips are listed under [request normalization](/reference/gateway-behavior/#request-normalization).

Where to go next

  <Link to="/reference/api/" className="tare-nav-card">
    Management API reference
    The complete field list for gateway auth policies, identity mapping rules, and project OIDC contracts, generated from the 0.7.0 specifications.
  </Link>
  <Link to="/agent-router-enterprise/guides/operate-and-govern/project-and-data-plane-management/provision-a-gateway/" className="tare-nav-card">
    Serve a project from a data plane
    The project label and the hostnames these credentials authenticate against.
  </Link>
  <Link to="/agent-router-enterprise/guides/operate-and-govern/onboard-developers-and-issue-keys/" className="tare-nav-card">
    Onboard developers and issue keys
    The credential most requests authenticate with, from issuance to organization-wide revocation.
  </Link>
  <Link to="/agent-router-enterprise/guides/identity-and-access-management/single-sign-on/sso-configuration-guide/" className="tare-nav-card">
    Configure SSO
    External identity for console sign-in, which is a separate surface from a request to a data plane.
  </Link>
