Packages

An OAuth 2.1 authorization server for Ash Framework apps — RFC 7591 dynamic client registration, PKCE, audience-bound JWTs, refresh-token rotation, and a built-in consent flow on top of ash_authentication + Phoenix.

Current section

Files

Jump to
ash_authentication_oauth2_server lib ash_authentication oauth2_server metadata.ex
Raw

lib/ash_authentication/oauth2_server/metadata.ex

# SPDX-FileCopyrightText: 2026 ash_authentication_oauth2_server contributors <https://github.com/ash-project/ash_authentication_oauth2_server/graphs/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).
"""
@spec protected_resource(server :: module()) :: map()
def protected_resource(server) do
%{
"resource" => server.resource_url(),
"authorization_servers" => [server.issuer_url()],
"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.
"""
@spec authorization_server(server :: module()) :: map()
def authorization_server(server) do
issuer = server.issuer_url()
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()
}
# Only advertise the DCR endpoint when it's actually enabled.
# Clients use this field to decide whether to attempt registration.
if server.dcr_enabled?(),
do: Map.put(base, "registration_endpoint", issuer <> "/oauth/register"),
else: base
end
end