AttestoPhoenix.Config (AttestoPhoenix v0.11.0)

Copy Markdown View Source

Configuration for the attesto_phoenix authorization-server layer.

This is the single source of truth consumed by every controller and plug in the library. It reads the host's configuration (from a host-chosen otp_app/config key), validates the required keys, applies neutral defaults, and derives the Attesto.Config the protocol layer needs.

Build one with new/1 (from a keyword list or map) or from_otp_app/2 (to read Application.get_env/2). Validation raises ArgumentError on a missing required key so misconfiguration fails fast at boot.

Keys

Required

  • :issuer - issuer URL (string) used as the JWT iss, the discovery issuer, and the base for endpoint URLs.
  • :keystore - module implementing Attesto.Keystore providing the signing key and the verification keys published via JWKS. Use a static keystore or a host KMS/HSM/Vault-backed implementation; per-key alg metadata is supported by the core keystore behaviour.
  • :repo - Ecto.Repo module used by the Ecto-backed code, refresh, nonce, and replay stores.
  • :load_client - (client_id -> {:ok, client} | {:error, :not_found} | {:error, :revoked}). Resolves an OAuth client. The host owns the client registry and revocation policy.

  • :verify_client_secret - (client, presented_secret -> boolean). Constant-time client-secret verification (e.g. via Attesto.SecureCompare). The host owns secret hashing.
  • :load_principal - (subject_id -> {:ok, principal} | {:error, :not_found}). Resolves the subject/principal during protected-resource authentication.

Optional callbacks

  • :authorize_scope - (client, requested_scope -> {:ok, granted_scope} | {:error, :invalid_scope}). Validates/narrows requested scope using Attesto.Scope algebra. Defaults to "subset of :scopes_supported".
  • :on_event - (%AttestoPhoenix.Event{} -> any). Audit/telemetry hook. No-op by default; the library never stores events itself.
  • :send_error - (conn, status, body_map -> conn). Optional transport hook used by AttestoPhoenix.OAuthError to serialize OAuth/OIDC errors into the host's API envelope while preserving the RFC status, challenge, and cache-control semantics.
  • :no_store - (conn -> conn). Optional transport hook used by AttestoPhoenix.OAuthError to apply no-store headers.
  • :www_authenticate - (conn, challenge_string -> conn). Optional transport hook used by AttestoPhoenix.OAuthError to write the WWW-Authenticate challenge header.
  • :resource_metadata - absolute URL of this resource's protected-resource metadata document (RFC 9728). When set, AttestoPhoenix.Plug.Authenticate advertises it as a resource_metadata auth-param on every WWW-Authenticate challenge it renders (RFC 9728 §5.1), so a client that is refused with 401 can discover which authorization server issues tokens for this resource. Omitted from the challenge when unset.
  • :basic_realm - realm string for token-endpoint Basic auth challenges. Default "OAuth".
  • :htu - (conn -> canonical_url_string). Overrides how the DPoP htu is computed behind proxies. Defaults to derivation from :trusted_proxies.
  • :cert_der - (conn -> der_binary | nil). Extracts the client mTLS certificate DER. Required only when :mtls_enabled.

  • :register_client - (metadata -> {:ok, client} | {:error, reason}). Persists a dynamically registered client. Required only when :registration_enabled.

  • :unregister_client - (client -> :ok | {:ok, client} | {:error, reason}). Deletes a dynamically registered client for registration management cleanup (RFC 7592). Optional; when unset, DELETE requests to the registration management endpoint fail closed.

  • :client_registration_access_token_hash - (client -> String.t() | nil). Extracts the stored hash of the registration access token issued with a dynamic client (RFC 7592). Optional; when unset, DELETE requests fail closed.

  • :introspection_authorize - (caller_client_id, response -> boolean). Authorizes the authenticated introspection caller against the token being introspected (RFC 7662 §4 / RFC 9701 §5). Consulted only for an active response; returning anything but true (or raising) downgrades the response to %{"active" => false} so a caller not entitled to the token learns nothing about it (FAPI: a regular client querying introspection is a leakage risk). response is the RFC 7662 member map (carrying aud, client_id, sub, scope, ...), letting the host match the token's audience/scope against the calling protected resource. Optional - when unset, every authenticated caller may introspect any token (the single-trust-domain default).
  • :principal_kinds - non-empty list of Attesto.PrincipalKind values or a zero-arity callback returning that list, passed into the core token configuration.
  • :build_principal - (client, subject, scope -> map). Builds the principal map passed to Attesto.Token.mint/3. The returned :sub MUST be namespaced with the matching Attesto.PrincipalKind sub_prefix: Attesto.Token rejects an unprefixed subject at mint time (:invalid_sub). This matters most for the client_credentials grant (RFC 6749 §4.4), where the principal subject is the OAuth client_id - and Dynamic Client Registration (RFC 7591 §3.2.1) issues that id unprefixed (the host's :register_client chooses it; the library imposes no namespace). :build_principal is the sole seam that applies the prefix; the prefix is mint-time defense-in-depth (an issued token's sub is unambiguous across principal kinds), not a substitute for it.
  • :build_userinfo_claims - (subject, granted_scopes, requested_claims -> claims_map). Produces the claim values the UserInfo endpoint (OpenID Connect Core §5.3) returns for the authenticated subject. The host owns the claim source (its user store); the library owns only the scope-to-claim shaping (OpenID Connect Core §5.4) and the guarantee that sub is present (OpenID Connect Core §5.3.2). granted_scopes is the list of scopes on the access token; requested_claims is the per-claim request map from the OpenID Connect claims parameter (%{} when none). Required only when the UserInfo endpoint is mounted.
  • :build_id_token_claims - (client, subject, granted_scopes, requested_claims -> claims_map). Produces the host claims merged into an ID Token (OpenID Connect Core §3.1.3.6 / §5.5 id_token member). Distinct from :build_userinfo_claims: it receives the resolved client, draws from the claims parameter's id_token member, and MUST NOT carry sub (the library sets the verified subject; a host-supplied sub is rejected by Attesto.IDToken). Optional - when unset the ID Token carries only the protocol claims.
  • :client_id - (client -> String.t()). Extracts the OAuth client identifier from the host's client struct.
  • :client_jwks - (client -> jwks). Returns the client's trusted public JWK Set for private_key_jwt client authentication. Required only for clients that authenticate with private_key_jwt.
  • :client_redirect_uris - (client -> [String.t()]). Returns the client's registered redirect URIs (RFC 6749 §3.1.2.2). The authorization endpoint exact-matches the request redirect_uri against this set (RFC 6749 §3.1.2.3); a client exposing none rejects every authorization request (fail closed).
  • :authenticate_resource_owner - (conn, request, auth_opts -> {:authenticated, subject} | {:halt, conn} | {:none} | {:error, :login_required | :consent_required | :interaction_required}). Establishes the resource owner for an authorization request (RFC 6749 §3.1, OIDC Core §3.1.2.3). Returns {:authenticated, subject} once a resource owner is known (a map carrying at least :subject, the OIDC sub, and optionally :auth_time, :acr, :amr), {:halt, conn} to take over the connection (e.g. redirect to a host login page that re-enters the authorization endpoint), {:none} when no subject can be established without UI, or an {:error, _} classifying why interaction is required (OIDC Core §3.1.2.6). auth_opts is a map carrying the OIDC Core §3.1.2.1 prompt/max_age directives the host must honour: :prompt, :force_reauth (prompt=login), :interactive (false for prompt=none, forbidding UI), and :max_age. The host owns all login UI; the library only invokes this hook. Required only when the authorization endpoint is mounted.
  • :consent - (conn, request, subject -> {:consented, subject} | {:halt, conn} | {:denied, reason}). Obtains the resource owner's consent for an authorization request (RFC 6749 §4.1.1). Returns {:consented, subject} to proceed (the returned subject may carry consent-derived claims), {:halt, conn} to take over the connection (e.g. render a consent screen that re-enters the authorization endpoint), or {:denied, reason} to refuse (reported to the client as access_denied, RFC 6749 §4.1.2.1). When unset, consent is implicitly granted for the authenticated subject.
  • :client_public? - (client -> boolean()). Returns whether a client may authenticate without a secret and rely on PKCE.
  • :client_requires_mtls? - (client -> boolean()). Returns whether a client requires mTLS-bound token issuance.
  • :client_requires_dpop? - (client -> boolean()). Returns whether a client requires DPoP-bound token issuance.
  • :client_grant_types - (client -> [String.t()] | nil). Returns the grant types registered for this client (RFC 7591 §2). When set, the token endpoint rejects a requested grant type not in the returned list.

  • :issue_refresh_token? - (client, granted_scope -> boolean()). Returns whether the authorization-code grant should issue an initial refresh token (RFC 6749 §6). When unset, the token controller issues one iff the granted scope contains offline_access (OIDC Core §11) and a :refresh_store is configured.
  • :code_store - module implementing Attesto.CodeStore.
  • :refresh_store - module implementing Attesto.RefreshStore.
  • :par_store - module implementing AttestoPhoenix.PARStore. Defaults to the single-node AttestoPhoenix.Store.PAR.ETS; use AttestoPhoenix.Store.EctoPARStore for a clustered/load-balanced deployment so a request_uri resolves on every node (FAPI 2.0 requires PAR).
  • :grant_types_supported - the grant types the server supports. Advertised as grant_types_supported (RFC 8414 §2), enforced by the token endpoint (a grant_type outside the set is rejected), and the accepted set for dynamic registration. Defaults to every implemented grant; narrow it to disable one (e.g. drop token-exchange) everywhere at once. See grant_types_supported/1.
  • :token_endpoint_auth_methods_supported - client authentication methods advertised/accepted by dynamic client registration and by the token/PAR endpoints when configured. When unset, all package-supported methods are accepted.

Optional values (with defaults)

  • :audience - default access-token audience (string or list).

  • :client_auth_signing_algs - the JOSE algorithms accepted for private_key_jwt client-assertion signatures, and the set advertised as token_endpoint_auth_signing_alg_values_supported in discovery. Defaults to Attesto.SigningAlg.fapi_algs/0 (PS256, ES256, EdDSA). A non-FAPI deployment can widen it; verification and the advertised metadata stay in lockstep because both read this one value.

  • :request_object_policy - an Attesto.RequestObject.Policy controlling verification of signed authorization request objects (JAR, RFC 9101). Defaults to %Attesto.RequestObject.Policy{} (generic OpenID Connect §6.1: nbf/exp/typ not required). For FAPI 2.0 Message Signing §5.3.1 set Attesto.RequestObject.Policy.fapi_message_signing(); the policy is then enforced both at the PAR endpoint and at /authorize.

  • :scopes_supported - list of supported scope strings (concrete and wildcard) advertised in discovery and used as the default scope catalog. For an OpenID Provider the reserved openid scope (OpenID Connect Core §3.1.2.1) is added to the OpenID Provider Metadata automatically by the core builder; it need not be listed here.

  • :bearer_methods_supported - the RFC 6750 access-token presentation methods the resource server accepts, advertised as bearer_methods_supported in the RFC 9728 protected-resource metadata document (/.well-known/oauth-protected-resource). A non-empty list of distinct methods, each "header" (§2.1) or "body" (§2.2) - the methods AttestoPhoenix.Plug.Authenticate accepts. The §2.3 "query" method is rejected: the plug never accepts a query-presented token, so advertising it would name a method the library cannot honour (and RFC 6750 §2.3 says it SHOULD NOT be used). Defaults to ["header", "body"], matching the plug; a host whose resource server accepts the token by Authorization header only sets ["header"] so the metadata describes exactly what it accepts.

  • :authorization_endpoint - absolute URL of the host-owned authorization endpoint (RFC 6749 §3.1 / OpenID Connect Discovery §3). The authorization endpoint runs the host's login/consent UI, so the library does not mount it; the host supplies the URL where it serves it. Advertised in the OpenID Provider Metadata; omitted when unset.

  • :userinfo_endpoint - absolute URL of the host-owned UserInfo endpoint (OpenID Connect Core §5.3). The host owns the claim source, so the library does not mount it; the host supplies the URL. Advertised in the OpenID Provider Metadata; omitted when unset.

  • :claims_supported - list of claim names the host's UserInfo endpoint and ID Tokens can return (OpenID Connect Discovery §3). Advertised in the OpenID Provider Metadata; omitted when unset.

  • :claims_parameter_supported - whether the provider accepts the OpenID Connect claims request parameter (OpenID Connect Discovery §3 / OpenID Connect Core §5.5). Default false: the authorization endpoint does not consume a claims parameter unless the host wires it, so the provider does not claim support for it. Advertised in the OpenID Provider Metadata only when set to true (the core builder treats absence as false per OpenID Connect Discovery §3).

  • :acr_values_supported - list of Authentication Context Class Reference values the provider can satisfy (OpenID Connect Discovery §3 / OpenID Connect Core §2). Advertised only when the host configures a non-empty list; omitted otherwise.

  • :ui_locales_supported - list of BCP47 (RFC 5646) language tags the provider's UI supports (OpenID Connect Discovery §3). Advertised only when the host configures a non-empty list; omitted otherwise.

  • :require_nonce - require the OpenID Connect nonce parameter on OpenID Connect Authentication Requests (OpenID Connect Core §3.1.2.1). Default false. When true, the authorization endpoint passes require_nonce: true to Attesto.AuthorizationRequest.validate/2 for a request whose scope contains openid, so a missing nonce on an OIDC request is rejected with a redirectable invalid_request error. A non-OpenID OAuth 2.0 request is never affected (RFC 6749 keeps the authorization code at SHOULD, never requiring a nonce). The host sets this per its own OpenID Provider policy.

  • :require_pushed_authorization_requests - require front-channel authorization requests to use a PAR request_uri issued by this server (RFC 9126). Default false.

  • :authorization_response_iss - include the RFC 9207 iss authorization response parameter on success and error redirects. Default true (authorization-server mix-up defense, mandated by FAPI 2.0); set false only for a deployment that must omit it.

  • :require_https - enforce HTTPS on the endpoints. Default true.

  • :trusted_proxies - list of trusted proxy CIDRs/IPs controlling whether X-Forwarded-* headers are honored. Default [] (no forwarded trust).

  • :access_token_ttl - access-token lifetime, seconds. Default 900.

  • :refresh_token_ttl - refresh-token lifetime, seconds. Default 1_209_600.

  • :refresh_token_rotation_grace_seconds - idempotency window, in seconds, during which a just-rotated refresh token can be retried and receive the same successor refresh token instead of being treated as a reuse attack. Default 60; set 0 for strict immediate reuse revocation. A non-zero window is important for clients that lose the first rotation response and retry the previous token (OAuth 2.0 Security BCP §4.13; FAPI 2.0 Security Profile §5.3.2.1).

  • :authorization_code_ttl - authorization-code lifetime, seconds. Default 60.

  • :dpop_enabled - enable DPoP sender-constraint support. Default true.

  • :dpop_nonce_required - require server-issued DPoP nonces. Default false.

  • :mtls_enabled - enable mTLS (RFC 8705) cnf binding. Default false.

  • :registration_enabled - enable /oauth/register. Default false.

  • :client_id_metadata - Client ID Metadata Document support - CIMD (draft-ietf-oauth-client-id-metadata-document-01, IETF OAuth WG). A keyword list configuring whether (and how) the authorization server dereferences an HTTPS client_id URL to a client metadata document. The whole feature is off by default; when enabled: true, discovery advertises client_id_metadata_document_supported and AttestoPhoenix.ClientIdMetadata.Resolver resolves a CIMD client_id through the configured fetcher and cache. Read it back with client_id_metadata/1 (the merged, defaulted keyword list) or the client_id_metadata_enabled?/1 predicate. Recognized members, with their defaults:

    • :enabled - master switch. Default false.
    • :fetcher - module implementing AttestoPhoenix.ClientIdMetadata.Fetcher (the SSRF-guarded outbound GET). Default AttestoPhoenix.ClientIdMetadata.Fetcher.Req. A host may override with its own HTTP stack or a CIMD proxy service.
    • :cache - module implementing AttestoPhoenix.ClientIdMetadata.Cache. Default AttestoPhoenix.ClientIdMetadata.Cache.Ecto (cluster-coherent); a single-node deployment may select AttestoPhoenix.ClientIdMetadata.Cache.ETS.
    • :allow_loopback - permit loopback addresses (the draft's "AS runs on loopback" exception; development only). Default false.
    • :max_document_bytes - body size cap for the fetched document (draft's recommended 5 KB). Default 5_120.
    • :request_timeout_ms - connect and receive timeout for the fetch. Default 5_000.
    • :cache_ttl_bounds - {min_seconds, max_seconds} the resolver clamps the response's Cache-Control: max-age / Expires freshness to (RFC 9111). Default {60, 86_400}.
    • :require_same_origin_redirect_uri - additionally require the request redirect_uri to be same-origin with the client_id URL, on top of the exact-match against the document's redirect_uris (draft §2 MAY, enforced by default here). Default true.
    • :allowed_hosts - optional allowlist of hostnames a CIMD client_id URL may resolve through; nil means "any public host" (subject to the fetcher's SSRF guard). Default nil.
    • :blocked_hosts - hostnames a CIMD client_id URL must never resolve through, checked before any network work. Default [].
  • :replay_check - DPoP jti replay check (module or {module, fun}). Defaults to the single-node ETS replay cache.

  • :nonce_store - Attesto.DPoP.NonceStore implementation. Defaults to the single-node ETS nonce store.

  • :sweep_interval_ms - interval for AttestoPhoenix.Store.Sweeper. The sweeper is not started if unset.

  • :table_prefix - optional Ecto schema/table prefix for the generated tables.

Endpoint paths advertised in metadata

The discovery documents (RFC 8414 §3, OpenID Connect Discovery §4) and the RFC 7591 §3.2.1 registration response advertise absolute endpoint URLs built from the :issuer and the request path each endpoint is mounted at. By default the OAuth endpoints live under /oauth/* (the historic surface), but a host that mounts them elsewhere (for example under /mcp/oauth/* to avoid colliding with a legacy provider) MUST advertise the paths it actually serves or clients are misdirected. These keys control that, all additive with defaults that reproduce the historic /oauth/* surface exactly:

  • :oauth_path_prefix - path segment prepended to every OAuth endpoint tail. Default "/oauth", yielding the historic /oauth/token, /oauth/par, etc. A host mounting under /mcp/oauth sets oauth_path_prefix: "/mcp/oauth" to advertise /mcp/oauth/token and so on. This is the FULL client-visible mount prefix, since the controllers cannot see the surrounding Phoenix scope. The well-known documents (RFC 8615) and the JWKS document stay anchored at the host root and are NOT relocated by this prefix.
  • :authorize_path, :token_path, :par_path, :revocation_path, :introspection_path, :registration_path, :userinfo_path - explicit per-endpoint path overrides. When set, the override wins over :oauth_path_prefix for that one endpoint (the integrator's "explicit endpoint overrides plus sane defaults"). Each defaults to nil, meaning "derive from :oauth_path_prefix". An override is an absolute path reference ("/custom/token"), advertised verbatim merged onto the issuer.

Use the resolver helpers (token_endpoint_url/1, par_endpoint_url/1, revocation_endpoint_url/1, registration_endpoint_url/1, userinfo_endpoint_url/1, authorize_endpoint_url/1, jwks_uri/1, and the resolved-path helpers token_path/1 and friends) rather than re-deriving the URLs in callers; the router macro derives its mounted-route tails from the same source so the mounted routes and the advertised routes cannot drift.

The loose *_client, *_principal, authorize_scope, consent, registration, and event callbacks above are grouped into named behaviours that document the full contract (with the governing RFC for each callback) and serve as the recommended production shape: AttestoPhoenix.ClientStore, AttestoPhoenix.PrincipalStore, AttestoPhoenix.ScopePolicy, AttestoPhoenix.ConsentPolicy, AttestoPhoenix.RegistrationStore, and AttestoPhoenix.EventSink. Wiring stays identical: pass an anonymous function, a {module, function} pair, or a {module, function, extra_args} triple per key as documented above. The behaviours are the contract; the Config keys are how a host installs an implementation.

Behaviour-module Config keys

Rather than wiring every host callback as an individual flat key, a host may install one behaviour module per concern and let the library resolve each callback from it:

Each per-callback value is resolved through the matching resolver fun on this module (client_id_fun/1, load_principal_fun/1, consent_fun/1, and so on) with a single precedence: the explicit flat key wins when set; otherwise, when a behaviour module is installed and exports the corresponding behaviour callback (after Code.ensure_loaded/1), the {module, function} pair is used; otherwise the resolution is nil (and the consumer's existing fail-closed default applies). Flat keys therefore never break: a host that wires the individual callbacks keeps the exact behaviour it had. new/1 validates at boot that any installed behaviour module is loadable and exports the callbacks it claims, so a typo'd or partial module fails fast rather than silently resolving to nil at request time.

Summary

Functions

Resolve the authenticate_resource_owner callback. See resolve_callback/2.

Absolute URL of the authorization endpoint: the issuer merged with authorize_path/1. Advertised in the OpenID Provider Metadata when the host does not supply a separate :authorization_endpoint.

The resolved request path of the authorization endpoint: the explicit :authorize_path override when set, otherwise :oauth_path_prefix joined with the conventional /authorize tail.

Resolve the authorize_scope callback. See resolve_callback/2.

Resolve the build_id_token_claims callback. See resolve_callback/2.

Resolve the build_principal callback. See resolve_callback/2.

Invokes the host's :build_userinfo_claims callback for the authenticated subject and returns the raw claims map it produces.

Resolve the build_userinfo_claims callback. See resolve_callback/2.

Resolve the client_grant_types callback. See resolve_callback/2.

Resolve the client_id callback. See resolve_callback/2.

Returns the merged, defaulted Client ID Metadata Document (CIMD) options.

Returns true iff Client ID Metadata Document support is enabled.

Resolve the client_jwks callback. See resolve_callback/2.

Resolve the client_public? callback. See resolve_callback/2.

Resolve the client_redirect_uris callback. See resolve_callback/2.

Resolve the client_registration_access_token_hash callback. See resolve_callback/2.

Resolve the client_requires_dpop? callback. See resolve_callback/2.

Resolve the client_requires_mtls? callback. See resolve_callback/2.

Resolve and load the host's client by client_id (RFC 6749 §2.2).

Resolve and run the host's constant-time client-secret verification (RFC 6749 §2.3.1) for client/presented_secret.

Resolve the consent callback. See resolve_callback/2.

Reads the config for otp_app under key (default AttestoPhoenix) from the application environment and builds a validated config.

The grant types the authorization server supports.

Absolute URL of the token introspection endpoint (RFC 7662): the issuer merged with introspection_path/1. Advertised as introspection_endpoint.

The resolved request path of the token introspection endpoint (RFC 7662). See authorize_path/1.

Absolute URL of the JWK Set document (RFC 7517 §5; the jwks_uri per RFC 8414 §2). The JWKS document is anchored at the host root under RFC 8615, so it is NOT relocated by :oauth_path_prefix.

Returns the merged, defaulted Identity Assertion JWT Authorization Grant (ID-JAG / jwt-bearer) options.

Returns true iff the Identity Assertion JWT Authorization Grant (ID-JAG / jwt-bearer) is enabled.

Resolve the load_client callback. See resolve_callback/2.

Resolve the load_principal callback. See resolve_callback/2.

Builds and validates a config from a keyword list or map.

Resolve the on_event callback. See resolve_callback/2.

Absolute URL of the pushed-authorization-request endpoint: the issuer merged with par_path/1. Advertised as pushed_authorization_request_endpoint (RFC 9126 §5).

The resolved request path of the pushed-authorization-request endpoint (RFC 9126). See authorize_path/1.

Resolve the register_client callback. See resolve_callback/2.

Absolute URL of an individual registered client's RFC 7592 management endpoint: the registration endpoint URL with the URL-encoded client_id appended. Returned as registration_client_uri in the RFC 7591 §3.2.1 client information response.

Absolute URL of the dynamic client registration endpoint: the issuer merged with registration_path/1. Advertised as registration_endpoint (RFC 7591 §3) only when registration is enabled.

The resolved request path of the dynamic client registration endpoint (RFC 7591). See authorize_path/1.

Resolve a configured callback by its flat key.

Resolve the resolve_jwt_bearer_subject callback. See resolve_callback/2.

Absolute URL of the revocation endpoint: the issuer merged with revocation_path/1. Advertised as revocation_endpoint (RFC 8414 §2, RFC 7009).

The resolved request path of the revocation endpoint (RFC 7009). See authorize_path/1.

Derives the Attesto.Config consumed by the protocol layer from this config.

Absolute URL of the token endpoint: the issuer merged with token_path/1. Advertised as token_endpoint (RFC 8414 §2).

The resolved request path of the token endpoint. See authorize_path/1.

Resolve the unregister_client callback. See resolve_callback/2.

Absolute URL of the UserInfo endpoint: the issuer merged with userinfo_path/1. Used when the host does not supply a separate :userinfo_endpoint.

The resolved request path of the UserInfo endpoint (OpenID Connect Core §5.3). See authorize_path/1.

Resolve the verify_client_secret callback. See resolve_callback/2.

Types

callback()

@type callback() :: function() | {module(), atom()} | {module(), atom(), [any()]}

t()

@type t() :: %AttestoPhoenix.Config{
  access_token_ttl: pos_integer(),
  acr_values_supported: [String.t()],
  audience: String.t() | [String.t()] | nil,
  authenticate_resource_owner: callback() | nil,
  authorization_code_ttl: pos_integer(),
  authorization_endpoint: String.t() | nil,
  authorization_response_iss: boolean(),
  authorize_path: String.t() | nil,
  authorize_scope: callback() | nil,
  basic_realm: String.t(),
  bearer_methods_supported: [String.t()],
  build_id_token_claims: callback() | nil,
  build_principal: callback() | nil,
  build_userinfo_claims: callback() | nil,
  cert_der: callback() | nil,
  claims_parameter_supported: boolean(),
  claims_provider: module() | nil,
  claims_supported: [String.t()],
  client_auth_signing_algs: [String.t()] | nil,
  client_grant_types: callback() | nil,
  client_id: callback() | nil,
  client_id_metadata: keyword(),
  client_jwks: callback() | nil,
  client_public?: callback() | nil,
  client_redirect_uris: callback() | nil,
  client_registration_access_token_hash: callback() | nil,
  client_requires_dpop?: callback() | nil,
  client_requires_mtls?: callback() | nil,
  client_store: module() | nil,
  code_store: module() | nil,
  consent: callback() | nil,
  consent_policy: module() | nil,
  dpop_enabled: boolean(),
  dpop_nonce_required: boolean(),
  event_sink: module() | nil,
  grant_types_supported: [String.t()] | nil,
  htu: callback() | nil,
  introspection_authorize: callback() | nil,
  introspection_path: String.t() | nil,
  issue_refresh_token?: callback() | nil,
  issuer: String.t(),
  jwt_bearer: keyword(),
  keystore: module(),
  load_client: callback(),
  load_principal: callback(),
  mtls_enabled: boolean(),
  no_store: callback() | nil,
  nonce_store: module() | nil,
  oauth_path_prefix: String.t(),
  on_event: callback() | nil,
  par_path: String.t() | nil,
  par_store: module() | nil,
  par_ttl: pos_integer(),
  principal_kinds: [Attesto.PrincipalKind.t()] | callback() | nil,
  principal_store: module() | nil,
  refresh_store: module() | nil,
  refresh_token_rotation_grace_seconds: non_neg_integer(),
  refresh_token_ttl: pos_integer(),
  register_client: callback() | nil,
  registration: module() | nil,
  registration_enabled: boolean(),
  registration_path: String.t() | nil,
  replay_check: callback() | module() | nil,
  repo: module(),
  request_object_policy: Attesto.RequestObject.Policy.t() | nil,
  require_https: boolean(),
  require_nonce: boolean(),
  require_pkce: boolean(),
  require_pushed_authorization_requests: boolean(),
  resolve_jwt_bearer_subject: callback() | nil,
  resource_metadata: String.t() | nil,
  revocation_path: String.t() | nil,
  scope_policy: module() | nil,
  scopes_supported: [String.t()],
  send_error: callback() | nil,
  sweep_interval_ms: pos_integer() | nil,
  table_prefix: String.t() | nil,
  token_endpoint_auth_methods_supported: [String.t()] | nil,
  token_path: String.t() | nil,
  trusted_proxies: [String.t()],
  ui_locales_supported: [String.t()],
  unregister_client: callback() | nil,
  userinfo_endpoint: String.t() | nil,
  userinfo_path: String.t() | nil,
  verify_client_secret: callback(),
  www_authenticate: callback() | nil
}

Functions

authenticate_resource_owner_fun(config)

@spec authenticate_resource_owner_fun(t()) :: callback() | nil

Resolve the authenticate_resource_owner callback. See resolve_callback/2.

authorize_endpoint_url(config)

@spec authorize_endpoint_url(t()) :: String.t()

Absolute URL of the authorization endpoint: the issuer merged with authorize_path/1. Advertised in the OpenID Provider Metadata when the host does not supply a separate :authorization_endpoint.

authorize_path(config)

@spec authorize_path(t()) :: String.t()

The resolved request path of the authorization endpoint: the explicit :authorize_path override when set, otherwise :oauth_path_prefix joined with the conventional /authorize tail.

authorize_scope_fun(config)

@spec authorize_scope_fun(t()) :: callback() | nil

Resolve the authorize_scope callback. See resolve_callback/2.

build_id_token_claims_fun(config)

@spec build_id_token_claims_fun(t()) :: callback() | nil

Resolve the build_id_token_claims callback. See resolve_callback/2.

build_principal_fun(config)

@spec build_principal_fun(t()) :: callback() | nil

Resolve the build_principal callback. See resolve_callback/2.

build_userinfo_claims(config, subject, scopes, requested)

@spec build_userinfo_claims(t(), String.t(), [String.t()], map()) :: map()

Invokes the host's :build_userinfo_claims callback for the authenticated subject and returns the raw claims map it produces.

The callback is applied with [subject, granted_scopes, requested_claims] (see the :build_userinfo_claims key documentation). It is the claim source for the UserInfo endpoint (OpenID Connect Core §5.3); the host owns the claim values, the controller owns the scope-to-claim shaping. Raises ArgumentError when the host has not configured the callback, so a mounted UserInfo endpoint cannot silently return an empty document.

build_userinfo_claims_fun(config)

@spec build_userinfo_claims_fun(t()) :: callback() | nil

Resolve the build_userinfo_claims callback. See resolve_callback/2.

client_grant_types_fun(config)

@spec client_grant_types_fun(t()) :: callback() | nil

Resolve the client_grant_types callback. See resolve_callback/2.

client_id_fun(config)

@spec client_id_fun(t()) :: callback() | nil

Resolve the client_id callback. See resolve_callback/2.

client_id_metadata(config)

@spec client_id_metadata(t()) :: keyword()

Returns the merged, defaulted Client ID Metadata Document (CIMD) options.

This is the host's :client_id_metadata keyword list merged over the library defaults (draft-ietf-oauth-client-id-metadata-document-01 §9), so every recognized member (:enabled, :fetcher, :cache, :allow_loopback, :max_document_bytes, :request_timeout_ms, :cache_ttl_bounds, :require_same_origin_redirect_uri, :allowed_hosts, :blocked_hosts) is always present. AttestoPhoenix.ClientIdMetadata.Resolver and the discovery wiring read the feature's configuration through this helper rather than reaching into the struct field directly.

client_id_metadata_enabled?(config)

@spec client_id_metadata_enabled?(t()) :: boolean()

Returns true iff Client ID Metadata Document support is enabled.

The feature is off unless the host sets client_id_metadata: [enabled: true]. Discovery advertises client_id_metadata_document_supported and the authorization endpoint resolves a CIMD client_id URL only when this is true.

client_jwks_fun(config)

@spec client_jwks_fun(t()) :: callback() | nil

Resolve the client_jwks callback. See resolve_callback/2.

client_public_fun(config)

@spec client_public_fun(t()) :: callback() | nil

Resolve the client_public? callback. See resolve_callback/2.

client_redirect_uris_fun(config)

@spec client_redirect_uris_fun(t()) :: callback() | nil

Resolve the client_redirect_uris callback. See resolve_callback/2.

client_registration_access_token_hash_fun(config)

@spec client_registration_access_token_hash_fun(t()) :: callback() | nil

Resolve the client_registration_access_token_hash callback. See resolve_callback/2.

client_requires_dpop_fun(config)

@spec client_requires_dpop_fun(t()) :: callback() | nil

Resolve the client_requires_dpop? callback. See resolve_callback/2.

client_requires_mtls_fun(config)

@spec client_requires_mtls_fun(t()) :: callback() | nil

Resolve the client_requires_mtls? callback. See resolve_callback/2.

client_store_load(config, client_id)

@spec client_store_load(t(), String.t()) :: term()

Resolve and load the host's client by client_id (RFC 6749 §2.2).

A required callback (:load_client / AttestoPhoenix.ClientStore); this helper invokes the resolved callback so consumers do not re-derive it.

client_store_verify_secret(config, client, presented_secret)

@spec client_store_verify_secret(t(), term(), String.t()) :: boolean()

Resolve and run the host's constant-time client-secret verification (RFC 6749 §2.3.1) for client/presented_secret.

from_otp_app(otp_app, key \\ __MODULE__)

@spec from_otp_app(atom(), atom()) :: t()

Reads the config for otp_app under key (default AttestoPhoenix) from the application environment and builds a validated config.

grant_types_supported(config)

@spec grant_types_supported(t()) :: [String.t()]

The grant types the authorization server supports.

Advertised as grant_types_supported (RFC 8414 §2) by both discovery documents and enforced by the token endpoint — a grant_type outside this set is rejected as unsupported_grant_type before dispatch. Defaults to every grant the token endpoint implements (["authorization_code", "refresh_token", "client_credentials", "urn:ietf:params:oauth:grant-type:token-exchange"]); configure :grant_types_supported to narrow it, e.g. drop urn:ietf:params:oauth:grant-type:token-exchange to disable token exchange across discovery, the token endpoint, and dynamic registration at once.

introspection_endpoint_url(config)

@spec introspection_endpoint_url(t()) :: String.t()

Absolute URL of the token introspection endpoint (RFC 7662): the issuer merged with introspection_path/1. Advertised as introspection_endpoint.

introspection_path(config)

@spec introspection_path(t()) :: String.t()

The resolved request path of the token introspection endpoint (RFC 7662). See authorize_path/1.

jwks_uri(config)

@spec jwks_uri(t()) :: String.t()

Absolute URL of the JWK Set document (RFC 7517 §5; the jwks_uri per RFC 8414 §2). The JWKS document is anchored at the host root under RFC 8615, so it is NOT relocated by :oauth_path_prefix.

jwt_bearer(config)

@spec jwt_bearer(t()) :: keyword()

Returns the merged, defaulted Identity Assertion JWT Authorization Grant (ID-JAG / jwt-bearer) options.

This is the host's :jwt_bearer keyword merged over the library defaults (draft-ietf-oauth-identity-assertion-authz-grant-04), so every recognized member (:enabled, :issuers, :assertion_max_lifetime_seconds, :allowed_resources, :jwks_resolver, :jwks_fetcher, :jwks_cache, :jwks_cache_ttl_bounds, :fetch_opts) is always present. AttestoPhoenix.AuthorizationServer.JwtBearer reads the feature's configuration through this helper.

jwt_bearer_enabled?(config)

@spec jwt_bearer_enabled?(t()) :: boolean()

Returns true iff the Identity Assertion JWT Authorization Grant (ID-JAG / jwt-bearer) is enabled.

The feature is off unless the host sets jwt_bearer: [enabled: true, ...]. When enabled, urn:ietf:params:oauth:grant-type:jwt-bearer is added to grant_types_supported/1 (so both discovery and the token endpoint honour it).

load_client_fun(config)

@spec load_client_fun(t()) :: callback() | nil

Resolve the load_client callback. See resolve_callback/2.

load_principal_fun(config)

@spec load_principal_fun(t()) :: callback() | nil

Resolve the load_principal callback. See resolve_callback/2.

new(opts)

@spec new(keyword() | map()) :: t()

Builds and validates a config from a keyword list or map.

Raises ArgumentError if a required key is missing or if a dependent key is absent for an enabled feature (e.g. :register_client when :registration_enabled, or :cert_der when :mtls_enabled).

on_event_fun(config)

@spec on_event_fun(t()) :: callback() | nil

Resolve the on_event callback. See resolve_callback/2.

par_endpoint_url(config)

@spec par_endpoint_url(t()) :: String.t()

Absolute URL of the pushed-authorization-request endpoint: the issuer merged with par_path/1. Advertised as pushed_authorization_request_endpoint (RFC 9126 §5).

par_path(config)

@spec par_path(t()) :: String.t()

The resolved request path of the pushed-authorization-request endpoint (RFC 9126). See authorize_path/1.

register_client_fun(config)

@spec register_client_fun(t()) :: callback() | nil

Resolve the register_client callback. See resolve_callback/2.

registration_client_uri(config, client_id)

@spec registration_client_uri(t(), String.t()) :: String.t()

Absolute URL of an individual registered client's RFC 7592 management endpoint: the registration endpoint URL with the URL-encoded client_id appended. Returned as registration_client_uri in the RFC 7591 §3.2.1 client information response.

registration_endpoint_url(config)

@spec registration_endpoint_url(t()) :: String.t()

Absolute URL of the dynamic client registration endpoint: the issuer merged with registration_path/1. Advertised as registration_endpoint (RFC 7591 §3) only when registration is enabled.

registration_path(config)

@spec registration_path(t()) :: String.t()

The resolved request path of the dynamic client registration endpoint (RFC 7591). See authorize_path/1.

resolve_callback(config, key)

@spec resolve_callback(t(), atom()) :: callback() | nil

Resolve a configured callback by its flat key.

Precedence (see the "Behaviour-module Config keys" section): the explicit flat key wins when set; otherwise the installed behaviour module wins when it exports the corresponding behaviour callback; otherwise nil. The result is a value an AttestoPhoenix.Callback.invoke/2,3 caller can run (an anonymous function, a {module, function} pair, a {module, function, extra_args} triple), or nil.

resolve_jwt_bearer_subject_fun(config)

@spec resolve_jwt_bearer_subject_fun(t()) :: callback() | nil

Resolve the resolve_jwt_bearer_subject callback. See resolve_callback/2.

revocation_endpoint_url(config)

@spec revocation_endpoint_url(t()) :: String.t()

Absolute URL of the revocation endpoint: the issuer merged with revocation_path/1. Advertised as revocation_endpoint (RFC 8414 §2, RFC 7009).

revocation_path(config)

@spec revocation_path(t()) :: String.t()

The resolved request path of the revocation endpoint (RFC 7009). See authorize_path/1.

to_attesto_config(config, extra \\ [])

@spec to_attesto_config(
  t(),
  keyword()
) :: Attesto.Config.t()

Derives the Attesto.Config consumed by the protocol layer from this config.

The protocol layer owns only the claim-level policy (:issuer, :audience, :keystore, the principal kinds, and the default access-token lifetime). The refresh/code TTLs and the DPoP/mTLS feature toggles are read directly from this struct by the controllers and plugs, so they are not duplicated into the Attesto.Config.

Pass principal_kinds: (a non-empty list of Attesto.PrincipalKind) and any other Attesto.Config.new/1 option as extra to complete the protocol config; they are merged over the values derived here.

token_endpoint_url(config)

@spec token_endpoint_url(t()) :: String.t()

Absolute URL of the token endpoint: the issuer merged with token_path/1. Advertised as token_endpoint (RFC 8414 §2).

token_path(config)

@spec token_path(t()) :: String.t()

The resolved request path of the token endpoint. See authorize_path/1.

unregister_client_fun(config)

@spec unregister_client_fun(t()) :: callback() | nil

Resolve the unregister_client callback. See resolve_callback/2.

userinfo_endpoint_url(config)

@spec userinfo_endpoint_url(t()) :: String.t()

Absolute URL of the UserInfo endpoint: the issuer merged with userinfo_path/1. Used when the host does not supply a separate :userinfo_endpoint.

userinfo_path(config)

@spec userinfo_path(t()) :: String.t()

The resolved request path of the UserInfo endpoint (OpenID Connect Core §5.3). See authorize_path/1.

verify_client_secret_fun(config)

@spec verify_client_secret_fun(t()) :: callback() | nil

Resolve the verify_client_secret callback. See resolve_callback/2.