AshAuthentication. Oauth2Server. CIMD
(ash_authentication_oauth2_server v0.3.0)
Copy Markdown
View Source
OAuth Client ID Metadata Documents (draft-ietf-oauth-client-id-metadata-document).
A CIMD client uses an HTTPS URL as its client_id; the URL points at a
JSON document describing the client (client_name, redirect_uris, …).
This lets clients and servers with no prior relationship interoperate
without a registration endpoint — the mechanism the MCP spec (2026-07-28)
now recommends over Dynamic Client Registration.
Enable it with cimd_enabled?: true on your Oauth2Server module. When
enabled, the RFC 8414 metadata document advertises
client_id_metadata_document_supported: true and the /oauth/authorize
endpoint accepts URL-shaped client_ids.
How resolved clients are stored
A validated metadata document is upserted into your regular client
resource, keyed by a cimd_url attribute — so authorization codes,
refresh tokens, and consent records keep referencing clients by their
UUID primary key exactly as they do for registered clients. The
document is re-fetched (subject to HTTP caching, honoured via
AshAuthentication.Oauth2Server.CIMD.Cache) on each authorization
request, so renamed clients and rotated redirect URIs are picked up;
the token, revocation, and error-redirect paths resolve the URL from
the database only and never trigger a fetch.
Client resource requirements
Your client resource needs (the installer scaffolds these for new apps):
attribute :cimd_url, :string, public?: true
identity :by_cimd_url, [:cimd_url]
create :register_cimd do
upsert? true
upsert_identity :by_cimd_url
accept [:cimd_url, :client_name, :redirect_uris, :grant_types,
:response_types, :token_endpoint_auth_method, :scope]
endFetching and SSRF
The fetch of an attacker-suppliable URL is the risky part; it goes
through the :cimd_fetcher module (default:
AshAuthentication.Oauth2Server.CIMD.ReqFetcher, which requires the
optional req dependency and applies a strict outbound policy — see
its docs). The authorize endpoint is the only place a fetch can be
triggered, and it is unauthenticated by protocol design — rate-limit it
like the other protocol endpoints (see the "Rate limiting" section in
AshAuthentication.Oauth2Server).
Summary
Functions
Look up an already-resolved CIMD client by its URL — database only, no fetch. Used by the token, revocation, and error-redirect paths, where the client must have been resolved during authorization already.
Resolve a URL client_id to a client record: fetch the metadata
document (through the cache), validate it, and upsert it into the
client resource.
Is this client_id value a CIMD-style URL?
Validate a fetched metadata document against the CIMD draft and this server's supported client shape.
Functions
@spec find_client(server :: module(), url :: String.t(), opts :: keyword()) :: {:ok, Ash.Resource.record()} | :error
Look up an already-resolved CIMD client by its URL — database only, no fetch. Used by the token, revocation, and error-redirect paths, where the client must have been resolved during authorization already.
@spec resolve_client(server :: module(), url :: String.t(), opts :: keyword()) :: {:ok, Ash.Resource.record()} | {:error, String.t()}
Resolve a URL client_id to a client record: fetch the metadata
document (through the cache), validate it, and upsert it into the
client resource.
Returns {:ok, client} or {:error, description} — the description is
safe to include in an OAuth invalid_client error response; fetch and
data-layer details are logged, not returned.
Is this client_id value a CIMD-style URL?
Per the draft, a CIMD client_id must be an https URL — anything
else is treated as an ordinary (opaque) client identifier.
Validate a fetched metadata document against the CIMD draft and this server's supported client shape.
Checks, in order: the document's client_id matches the URL it was
fetched from exactly (no normalization — per the draft), a
client_name is present (required by the MCP spec; it's what the
consent screen shows the user), and the redirect_uris /
grant_types / response_types / token_endpoint_auth_method
fields pass the same validation Dynamic Client Registration applies.