# SPDX-FileCopyrightText: 2026 ash_authentication_oauth2_server contributors # # SPDX-License-Identifier: MIT defmodule AshAuthentication.Oauth2Server.Metadata do @moduledoc """ Builders for the discovery metadata endpoints. * `protected_resource/1` (RFC 9728) โ€” for the resource server, served at `/.well-known/oauth-protected-resource`. * `authorization_server/1` (RFC 8414) โ€” for the authorization server, served at `/.well-known/oauth-authorization-server`. Both return plain maps; controllers JSON-encode them. """ @doc """ Build the OAuth Protected Resource Metadata document (RFC 9728). `context` is forwarded to the server's `resource_url/1` and `issuer_url/1` callbacks so per-request (e.g. per-tenant) resolution works. Single-tenant callers can pass `%{}`. """ @spec protected_resource(server :: module(), context :: map()) :: map() def protected_resource(server, context \\ %{}) do %{ "resource" => server.resource_url(context), "authorization_servers" => [server.issuer_url(context)], "scopes_supported" => server.scopes(), "bearer_methods_supported" => ["header"] } end @doc """ Build the OAuth Authorization Server Metadata document (RFC 8414). Endpoint paths are derived from the `issuer_url` so that mounting under a custom prefix works without configuration. `context` is forwarded to `issuer_url/1` so per-tenant deployments can resolve the issuer from the current request. """ @spec authorization_server(server :: module(), context :: map()) :: map() def authorization_server(server, context \\ %{}) do issuer = server.issuer_url(context) base = %{ "issuer" => issuer, "authorization_endpoint" => issuer <> "/oauth/authorize", "token_endpoint" => issuer <> "/oauth/token", "revocation_endpoint" => issuer <> "/oauth/revoke", "response_types_supported" => ["code"], "grant_types_supported" => ["authorization_code", "refresh_token"], "code_challenge_methods_supported" => ["S256"], "token_endpoint_auth_methods_supported" => ["none"], "scopes_supported" => server.scopes(), # RFC 9207 โ€” we include `iss` in every authorization response, and # advertising that is a MUST once we do (RFC 9207 ยง2.3). "authorization_response_iss_parameter_supported" => true } base # Only advertise the DCR endpoint when it's actually enabled. # Clients use this field to decide whether to attempt registration. |> put_if(server.dcr_enabled?(), "registration_endpoint", issuer <> "/oauth/register") # Clients check this before using a URL as their client_id (and fall # back to DCR / pre-registration when it's absent). |> put_if(server.cimd_enabled?(), "client_id_metadata_document_supported", true) end defp put_if(map, true, key, value), do: Map.put(map, key, value) defp put_if(map, false, _key, _value), do: map end