> ## Documentation Index
> Fetch the complete documentation index at: https://docs.strata.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Client ID Metadata Documents

<Warning>
  Client ID Metadata Documents are an experimental feature -- see [Experimental Features](/reference/orchestrator/experimental/overview) for important caveats.
</Warning>

Traditional OAuth requires every client to be registered ahead of time -- an operator creates a `clientID`, issues credentials, and records redirect URIs before the client can authenticate. This works for a fixed set of applications, but breaks down for the growing population of AI agents and MCP clients that appear dynamically and cannot be pre-provisioned.

Client ID Metadata Documents (CIMD) solve this by letting a client identify itself with an `https` URL instead of a pre-registered ID. The client's `client_id` **is** a URL that serves a JSON metadata document describing the client. When the Orchestrator receives an authorization request from an unknown URL `client_id`, it fetches that document, validates it, caches it, and builds a client from it on the fly -- no manual registration required. This implements [draft-ietf-oauth-client-id-metadata-document](https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/).

<Tip>
  CIMD is the mechanism modern AI agents use to authenticate. For example, [Claude Code](/guides/ai-identity/connect/claude) presents its published metadata URL as its `client_id` and requests no scope -- the Orchestrator resolves it to a public client and issues tokens without any per-client setup. See the [AI Identity Gateway](/reference/modes/ai-identity-gateway) and [MCP Proxy](/guides/ai-identity/mcp-proxy) guides for related patterns.
</Tip>

## How it works

An operator enables CIMD by adding one or more `oidcCimd` apps to an Orchestrator that already runs an OIDC Provider (the top-level `oidcProvider` configuration). Each app is a **domain-scoped policy**: it lists the domains whose metadata URLs it will resolve, and the authentication, authorization, and token settings to apply to clients from those domains.

```mermaid theme={null}
sequenceDiagram
    participant Client as CIMD Client
    participant Orch as Orchestrator (OIDC Provider)
    participant Doc as client_id URL

    Client->>Orch: authorize (client_id=https://app.example.com/oauth-client)
    Orch->>Orch: match host against oidcCimd allowedDomains
    Orch->>Doc: GET metadata document (SSRF-safe, no redirects)
    Doc-->>Orch: JSON metadata (redirect_uris, grant_types, ...)
    Orch->>Orch: validate + cache metadata, build client
    Orch->>Client: run authorization code + PKCE flow
    Client->>Orch: token request
    Orch-->>Client: access token (+ refresh token)
```

Once resolved, a CIMD client behaves like any other public or confidential OIDC client: it completes the authorization code flow (with PKCE) and receives tokens signed by the OIDC Provider. Resolved metadata is cached so subsequent requests from the same client skip the fetch.

## The metadata document

The document served at the `client_id` URL is a JSON object using the client metadata fields defined by [OAuth Dynamic Client Registration (RFC 7591)](https://datatracker.ietf.org/doc/html/rfc7591#section-2) and profiled by [draft-ietf-oauth-client-id-metadata-document](https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/). Refer to those specifications for the full field registry and semantics.

The Orchestrator reads a subset of those fields -- `client_id`, `redirect_uris`, `grant_types`, `response_types`, `token_endpoint_auth_method`, `jwks_uri`, `scope`, and `client_name` -- and applies the constraints described in [Client requirements](#client-requirements) below.

A minimal metadata document for a public client looks like:

```json theme={null}
{
  "client_id": "https://app.example.com/oauth-client",
  "client_name": "Example Agent",
  "redirect_uris": ["https://app.example.com/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}
```

## Client requirements

The Orchestrator enforces the following rules. A metadata document that violates any of them makes the client unresolvable, and the authorization request is rejected as an invalid client.

**`client_id` URL** -- must use the `https` scheme, include a path, and must not contain dot-segments (`.` / `..`), a fragment, userinfo, or a query string. The host must match one of the configured `allowedDomains`.

**`redirect_uris`** -- at least one is required. Each must use `https`, except that `http` is permitted for loopback hosts (`localhost`, `127.0.0.1`, `::1`) per [RFC 8252](https://datatracker.ietf.org/doc/html/rfc8252). Loopback redirects may use any port.

**`grant_types`** -- only `authorization_code` and `refresh_token` are supported, and the field defaults to `authorization_code` when omitted. Declaring `refresh_token` requires `authorization_code` as well. Implicit, hybrid, client credentials, and other grants are rejected.

**`response_types`** -- only `code` (authorization code flow) is supported, and the field defaults to `code` when omitted.

**`token_endpoint_auth_method`** -- defaults to `none` when omitted:

* `none` -- the client is **public** and MUST use PKCE.
* `private_key_jwt` -- the client is **confidential**; its `jwks_uri` must be an `https` URL sharing the same origin (scheme + host + port) as the `client_id`. The Orchestrator fetches the JWKS to verify the client's signed authentication assertions.

**`scope`** -- optional. When present, requestable scopes are restricted to this set; when omitted, scopes are unrestricted (like a static client).

<Note>
  When a client declares the `refresh_token` grant, the Orchestrator automatically grants the `offline_access` scope so a refresh token is issued -- even if the client requests no scope. This is required for agents such as Claude Code that send an empty scope but still need to refresh tokens without re-running the full authorization flow.
</Note>

## Configuration

CIMD is configured as one or more apps of type `oidcCimd` alongside the top-level `oidcProvider` configuration. Each `oidcCimd` app registers a single domain-scoped policy.

| Field                      | Type              | Required | Description                                                                                                                                                                                   |
| -------------------------- | ----------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `allowedDomains`           | string\[]         | yes      | Domains whose metadata URLs this policy resolves. Each entry is an exact hostname (`app.example.com`) or a wildcard prefix (`*.example.com`). A wildcard is only valid as the leftmost label. |
| `authentication`           | object            | yes      | How users are authenticated -- an `idps` list, or `isAuthenticatedSE` + `authenticateSE` service extensions. Same shape as an `oidc` app.                                                     |
| `authorization`            | object            | no       | Authorization policy applied to resolved clients. Defaults to allow-all.                                                                                                                      |
| `allowedAudiences`         | string\[]         | no       | Audiences CIMD clients may request in access tokens.                                                                                                                                          |
| `claimsMapping`            | map               | no       | Maps user attributes to token claims, namespaced by connector (e.g. `email: azure.email`).                                                                                                    |
| `accessToken`              | object            | no       | Access token `type` (`jwt` / `opaque`), `length`, and `lifetimeSeconds`.                                                                                                                      |
| `refreshToken`             | object            | no       | Refresh token `length` and `lifetimeSeconds`.                                                                                                                                                 |
| `attrProviders`            | object\[]         | no       | Connectors used to load attributes for resolved clients.                                                                                                                                      |
| `loadAttrsSE`              | service extension | no       | Customizes attribute loading.                                                                                                                                                                 |
| `buildIDTokenClaimsSE`     | service extension | no       | Adds custom claims to the ID token.                                                                                                                                                           |
| `buildAccessTokenClaimsSE` | service extension | no       | Adds custom claims to the access token.                                                                                                                                                       |

### Example

An OIDC Provider with a single CIMD policy that resolves any client under `*.example.com` and authenticates users against an Azure connector:

```yaml maverics.yaml theme={null}
# The OIDC Provider is configured under the top-level `oidcProvider` key.
oidcProvider:
  discovery:
    issuer: https://sso.company.com
    endpoints:
      wellKnown: /.well-known/openid-configuration
      auth: /oauth2/authorize
      token: /oauth2/token
      jwks: /oauth2/jwks
      userinfo: /oauth2/userinfo
  jwks:
    - algorithm: RSA256
      privateKey: <oidc-signing-key>

# CIMD policies are registered as apps of type `oidcCimd`.
apps:
  - name: partner-agents
    type: oidcCimd
    # Resolve CIMD client_id URLs under these domains.
    allowedDomains:
      - "*.example.com"
    authentication:
      idps:
        - azure
    allowedAudiences:
      - "https://api.company.com"
    accessToken:
      type: jwt
      lifetimeSeconds: 3600
    refreshToken:
      lifetimeSeconds: 2592000
```

### Multiple policies

You can register several `oidcCimd` apps to apply different templates to different domains. When more than one policy could match a host, the **most specific** domain pattern wins: an exact hostname outranks any wildcard, and a wildcard with more labels outranks one with fewer. Exact-duplicate domain strings across policies are rejected at startup.

```yaml maverics.yaml theme={null}
apps:
  # Broad policy for all partner subdomains.
  - name: partner-agents
    type: oidcCimd
    allowedDomains:
      - "*.example.com"
    authentication:
      idps:
        - azure

  # More specific policy that wins for this exact host, with stricter settings.
  - name: privileged-agent
    type: oidcCimd
    allowedDomains:
      - "agent.example.com"
    authentication:
      idps:
        - azure
    # Stricter authorization: only members of the privileged group may use this client.
    authorization:
      rules:
        - and:
            - contains: ["{{ azure.groups }}", "privileged-agents"]
    accessToken:
      lifetimeSeconds: 300
```

## Security and caching

Because the Orchestrator fetches a client-supplied URL, CIMD metadata retrieval is hardened against server-side request forgery (SSRF):

* **Special-use IP blocking** -- resolved IPs are checked against [RFC 6890](https://datatracker.ietf.org/doc/html/rfc6890) special-use ranges (loopback, private, link-local, etc.) and rejected. The connection is made to the validated IP to prevent DNS rebinding.
* **No redirects** -- the metadata fetch does not follow HTTP redirects.
* **Bounded requests** -- a 5-second timeout and a \~5 KB response size cap, and the response must be `application/json`.
* **Domain allowlist** -- only hosts matching a policy's `allowedDomains` are fetched at all.

Resolved metadata is cached to avoid re-fetching on every request:

* The cache TTL is derived from the response's `Cache-Control: max-age`, clamped to a minimum of **5 minutes** and a maximum of **24 hours**.
* Up to **1000** metadata documents are cached per policy.
* When a cached document is refreshed and a security-relevant field changes (`token_endpoint_auth_method`, `jwks_uri`, or `redirect_uris`), the change is logged.

## Discovery

When at least one `oidcCimd` policy is registered, the OIDC Provider advertises CIMD support in its discovery (well-known) document:

* `client_id_metadata_document_supported` is set to `true`.
* `token_endpoint_auth_methods_supported` gains `none` and `private_key_jwt` (the methods CIMD honors) alongside the static `client_secret_post`.

With no CIMD policies configured, the discovery document is unchanged and `client_id_metadata_document_supported` is `false`.

## Examples

The following `oidcCimd` app configurations allow common AI clients to authenticate using CIMD without pre-registering a client ID. In each case, the client presents an `https` URL as its `client_id`; the Orchestrator matches the host against `allowedDomains`, fetches the metadata document, and completes the authorization code flow automatically.

<Tabs>
  <Tab title="Claude Code / Claude Desktop">
    Both Claude Code and Claude Desktop present a metadata URL on `claude.ai` as the `client_id`. A single `oidcCimd` policy covers both.

    ```yaml maverics.yaml theme={null}
    apps:
      - name: claude
        type: oidcCimd
        allowedDomains:
          - claude.ai
        authentication:
          idps:
            - upstream-idp
        accessToken:
          type: jwt
          lifetimeSeconds: 3600
        refreshToken:
          lifetimeSeconds: 86400
        claimsMapping:
          email: upstream-idp.email
          name: upstream-idp.name
          sub: upstream-idp.sub
    ```

    <Note>
      When Claude Desktop connects via [`mcp-remote`](https://github.com/geelen/mcp-remote) as a local stdio proxy, CIMD does not apply — the client ID is a plain string, not a URL, and must be registered as a standard OIDC app. See [Connect Claude to the Gateway](/guides/ai-identity/connect/claude) for mcp-remote configuration.
    </Note>
  </Tab>

  <Tab title="ChatGPT">
    ChatGPT presents a metadata URL on `chatgpt.com` as its `client_id`.

    ```yaml maverics.yaml theme={null}
    apps:
      - name: chatgpt
        type: oidcCimd
        allowedDomains:
          - chatgpt.com
        authentication:
          idps:
            - upstream-idp
        accessToken:
          type: jwt
          lifetimeSeconds: 3600
        refreshToken:
          lifetimeSeconds: 86400
        claimsMapping:
          email: upstream-idp.email
          name: upstream-idp.name
          sub: upstream-idp.sub
    ```

    <Note>
      Verify the actual `client_id` URL ChatGPT presents during the authorization
      flow — the host determines which `allowedDomains` entry applies. If the host
      differs from `chatgpt.com` (for example, `openai.com`), update `allowedDomains`
      accordingly. You can find the `client_id` in the Orchestrator's authorization
      logs on the first connection attempt.
    </Note>
  </Tab>
</Tabs>

***

## Related Pages

<CardGroup cols={2}>
  <Card title="Experimental Features" icon="flask" href="/reference/orchestrator/experimental">
    Overview of all experimental features and important caveats
  </Card>

  <Card title="OIDC Provider" icon="openid" href="/reference/modes/oidc-provider">
    Configure the Orchestrator as an OIDC authorization server
  </Card>

  <Card title="AI Identity Gateway" icon="robot" href="/reference/modes/ai-identity-gateway">
    Secure AI agent-to-tool communication via MCP
  </Card>

  <Card title="Connect Claude" icon="link" href="/guides/ai-identity/connect/claude">
    Connect Claude Code and other Anthropic clients
  </Card>
</CardGroup>
