Skip to main content
Version: 0.2.0

Client ID Metadata Documents

A Client ID Metadata Document, or CIMD, removes the registration step. Your client_id is an https URL. That URL serves a small JSON document describing your application. SAG fetches it when your application first appears.

Nothing is registered anywhere, because the document's URL is the identity. Only somebody who controls that origin can change what your application claims to be.

The shortest possible one​

Serve this at https://ledger.example.com/oauth-client, with Content-Type: application/json:

{
"client_id": "https://ledger.example.com/oauth-client",
"client_name": "Ledger",
"redirect_uris": ["https://ledger.example.com/auth/callback"]
}

Then use that URL as your client_id. That is the entire onboarding process.

The rules​

SAG will refuse a document that breaks any of these:

  1. The URL is the client ID. SAG uses the URL it fetched as your client ID. The client_id field is optional and may name a different client, such as a native application that redirects to localhost.
  2. There must be a well-formed redirect URI. It is matched exactly when SAG returns an authorisation code. A metadata publisher is trusted to declare where its own codes go.
  3. No redirects while fetching. SAG will not follow one. If it did, an open redirect could make a permitted URL serve metadata from elsewhere.
  4. There is a size cap. The document is small by nature, and a cap means a hostile or broken URL cannot be used to exhaust the fetcher.
  5. A jwks_uri stays on your origin. This stops the metadata document making SAG fetch token-authentication keys from somewhere else.
  6. The host must be a public one. SAG resolves the URL's A and AAAA records before fetching, and refuses the document if any answer is loopback, private, link-local, or a private address tunnelled inside an IPv6 one. A client_id that only resolves inside somebody's network is a way to make SAG fetch from inside its own. Development mode permits localhost and nothing else private.

Such a client is public​

The document is readable by anybody, so it can hold no secret. That is not a limitation to work around; it is what the design says. SAG therefore requires PKCE from a CIMD client whatever the document says about it.

If your application needs to authenticate at the token endpoint, publish a jwks or jwks_uri in the document and use private_key_jwt. That is the only way a self-describing client stops being public, and it is the right answer for anything long-lived:

{
"client_id": "https://ledger.example.com/oauth-client",
"client_name": "Ledger",
"redirect_uris": ["https://ledger.example.com/auth/callback"],
"token_endpoint_auth_method": "private_key_jwt",
"jwks_uri": "https://ledger.example.com/oauth-client/jwks.json"
}

The private half never leaves your application, and no shared secret ever crosses the wire.

Fields worth setting​

FieldWhy
client_nameShown to the person: "Continue to Ledger". Set it, or they see a URL
redirect_urisRequired. Matched exactly
post_logout_redirect_urisWhere sign-out may return to
tos_uri, policy_uriYour terms and privacy links, shown on the sign-in screens
logo_uriYour logo, shown above the sign-in heading. https only
jwks_uri or jwksOnly if you are using private_key_jwt

The one practical constraint​

The client_id URL has to resolve to the same place twice: once for the browser following a redirect, and once for SAG fetching the document server-side. Anything that makes those two differ will break it.

In practice this catches people out with split-horizon DNS, a document behind a VPN or an IP allowlist, or a local development hostname that only exists on the developer's machine. If SAG cannot fetch the document from where it runs, the client does not exist as far as SAG is concerned - and a hostname that resolves to a private address is refused outright rather than attempted, as above.

For local development against your own instance, run SAG and the relying party where each can reach the other. The local stack demonstrates exactly this, with a CIMD client as one of four applications signing in.

Caching, and changing the document​

SAG caches a fetched document for a short period, so an edit is not instantaneous. Adding a redirect URI is not a change to make five minutes before you need it. On your own deployment the cache lifetime is CLIENTS_CIMD_CACHE_TTL; on the hosted gateway it is whatever RESOAuth® has configured.

If CIMD does not fit​

CIMD is not compulsory in general, but it is the only way to connect an application to the shared hosted gateway today - there is no manual registration route. See beyond a public client for what that rules out, and how to ask about it.

On your own deployment, the same four ways of describing a relying party are in the relying parties reference, along with every CLIENTS_CIMD_* switch that controls this behaviour.