AttestoPhoenix.Config (AttestoPhoenix v3.2.1)

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.

Controller actions resolve the already validated request configuration with resolve!/1, which reads %AttestoPhoenix.Config{} from conn.private[:attesto_phoenix_config]. The zero-argument resolve!/0 remains available for non-request code that intentionally reads the global application configuration.

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 - absolute HTTPS issuer URL (string, with no query or fragment) used as the JWT iss, the discovery issuer, and the base for endpoint URLs. The bundled router's root well-known routes require an origin-only issuer; a path-bearing issuer requires standards-derived well-known routes mounted by the host. Derived endpoint URLs are resolved against the issuer origin: an issuer path is not prepended to the resolved endpoint paths, so a path-bearing issuer must also set :oauth_path_prefix (or the per-endpoint path overrides) to advertise its endpoints under that path.
  • :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.
  • :vc_keystore - the keystore used to sign issued Verifiable Credentials; defaults to :keystore. Configure a separate EC/ES256 keystore here to issue ES256-signed credentials (e.g. for HAIP) while ID tokens keep their own signing key.
  • :verifier_encryption_keystore - a dedicated EC P-256 keystore used only to advertise and decrypt encrypted OID4VP direct_post.jwt responses. It has no fallback to :keystore; encrypted-response request creation fails closed when this setting is absent or is not a usable private P-256 key.
  • :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 HTTPS URL (with no fragment) 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.

  • :resource_metadata_resolver - (conn -> absolute_url | nil). Optional request-aware override for :resource_metadata. When configured, its result selects the RFC 9728 metadata URI for that protected-resource request; returning nil deliberately omits the auth-param. This lets one origin serve multiple resource identifiers without pointing every challenge at one global document. An invalid callback result is omitted rather than advertised. The resolver is consulted once per protected-resource request - including requests that authenticate successfully, because the selected URI must be in place before verification renders any challenge - so it should be fast and total. Exceptions raised by the resolver are not rescued and abort the request, successful ones included. When unset, the static :resource_metadata value retains its existing behavior. The callback is trusted configuration: return pinned or allowlisted metadata URLs and do not derive their authority from request Host, forwarded, query, or header values.

  • :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). Deprecated compatibility alias for terminator-provided certificate extraction. It is invoked only when the adapter-reported immediate socket peer matches :trusted_proxies. Migrate header readers to :forwarded_cert_der. Direct TLS adapters expose the authenticated certificate through Plug.Conn.get_peer_data/1 and need no callback.

  • :forwarded_cert_der - (conn -> der_binary | nil). Extracts a client certificate forwarded by a TLS terminator. Unlike :cert_der, this callback is invoked only when the immediate peer matches :trusted_proxies; an untrusted request can never make its certificate header authoritative. The terminator MUST remove any client-supplied copy of the header and replace it only after a successful client TLS handshake; the application listener SHOULD be network-reachable only from those trusted terminators.

  • :client_certificate_chain_validated? - (conn, der -> boolean). Confirms that the TLS terminator validated the presented certificate's PKI chain, validity, and applicable revocation policy. Required for tls_client_auth; the self-signed method does not use PKI validation.

  • :client_mtls_metadata - (client -> map | {:ok, map} | nil | {:error, reason}). Returns the client's RFC 8705 registration metadata, including token_endpoint_auth_method and exactly one PKI subject field for tls_client_auth. Return nil only when the client has no mTLS registration; errors and malformed results fail authentication closed. For self_signed_tls_client_auth, :client_jwks supplies the resolved JWK Set.

  • :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).

  • :resource_indicators - RFC 8707 resource policy as [allowed_resources: [...], allowed_resources_for: callback]. Static entries are absolute resource URIs. The optional one-argument callback receives the original OAuth client and returns additional identifiers that client may target. The same policy governs issuance, introspection, and token-exchange subject-token verification.

  • :native_apps - RFC 8252 (BCP 212) native-app profile options as [loopback_redirect: true, loopback_include_localhost: false, reject_embedded_user_agents: false].

    The profile as a whole is off until the host classifies a client with :client_native?, which defaults to false. That callback — not anything here — is what keeps an unconfigured deployment byte-identical.

    :loopback_redirect is an opt-out, defaulting to true. RFC 8252 §7.3 states the port allowance as a MUST, so it follows from a client being marked native: a redirect_uri of http://127.0.0.1/... or http://[::1]/... matches the registered URI on any port, while scheme, host, path, and query still compare exactly. Nothing else is relaxed — https, private-use schemes, remote hosts, and the hostname localhost (§8.3 discourages it) all stay exact-match, as does every non-native client. Set it false to forbid the exception server-wide: an operator kill switch, or a deployment certifying against a profile that mandates exact redirect-URI matching (the OpenID Connect and FAPI profiles do). Such a deployment normally has no native clients to begin with, so the usual answer is simply not to mark any.

    :loopback_include_localhost is an opt-in, defaulting to false. It widens the §7.3 port allowance to the bare hostname localhost, selecting Attesto.RedirectURI's :exact_allow_loopback_port_including_localhost mode wherever :exact_allow_loopback_port would have been selected. §8.3 recommends the IP literal over the name, but its reasons are all about what the client binds and how the user's device resolves names - a server refusing the request changes none of them - and real native clients (Claude Code's published metadata document among them) register a portless http://localhost/callback and bind an ephemeral port, which no strict deployment can serve at all. The name stays a distinct host identity: localhost never cross-matches 127.0.0.1 or [::1], and everything else about the exception keeps its exact-match discipline (see Attesto.RedirectURI for the full constraint list). It is subordinate to :loopback_redirect: when that kill switch is false, this option has no effect.

    :reject_embedded_user_agents enables the RFC 8252 §8.12 recommendation that the authorization endpoint refuse requests made from an in-app webview, which the host application can use to observe the user's credentials. Detection is a User-Agent heuristic (AttestoPhoenix.RequestContext.embedded_user_agent?/1) and therefore produces false positives, which is why it is opt-in and applies to every client rather than only native ones — the embedding app is not necessarily the OAuth client.

    The remaining RFC 8252 obligations (§8.1 PKCE, §8.4 client authentication) likewise follow from :client_native? alone: they are strictly additional restrictions on a client the host has deliberately classified as native.

  • :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. The protocol layer injects the authenticated OAuth client_id claim required by RFC 9068; the callback may omit it or return the same value, but a conflicting value fails issuance.

  • :authorization_grant_id_claim - optional access-token claim name for a stable, opaque authorization-code family identifier. When configured, authorization-code and descendant refresh access tokens carry the authoritative family_id; all other grant types omit the claim. A code that carried no usable family_id is permanently ineligible: redemption may still create an internal refresh family, but neither its initial nor any refreshed access token carries the claim. Within access tokens the library owns the value and strips a host-supplied one before signing; ID Token and UserInfo callback output is host-owned and unfiltered. Disabled by default.

  • :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_credential - (subject, credential_configuration_id, holder_jwk -> {:ok, credential} | {:error, reason}). Produces the format-specific claim material and optional validity window for the OID4VCI Credential endpoint. For SD-JWT VC and JWT VC, the library binds holder_jwk as cnf; for mdoc, it binds the key as the MSO device key. The library signs the resulting credential. Required when credential issuance is mounted.

  • :build_deferred_credential - (subject, transaction_id -> {:ok, credential} | {:error, :issuance_pending} | {:error, reason}). Completes a previously deferred credential (OID4VCI §9) for the Deferred Credential endpoint. The returned credential type and claim values are signed into an SD-JWT VC the same way :build_credential's are, minus holder-key binding (the endpoint carries no fresh proof). {:error, :issuance_pending} reports the OID4VCI issuance_pending error so the wallet retries later; any other error maps to invalid_credential_request. Required only when the Deferred Credential endpoint is used.

    Security — this callback owns the ownership check. The library authenticates the caller's access token and passes you its verified subject, but it does not (and cannot) know which subject a transaction_id belongs to — deferred-issuance state lives in your store. You MUST resolve the transaction scoped to subject and return an error (not the credential) when the transaction_id was not issued to that subject. Resolving by transaction_id alone is an IDOR: any authenticated wallet could poll another wallet's transaction_id and receive that holder's credential. Additionally, mint the transaction_id with a CSPRNG (e.g. Attesto.Secret.generate/0, 256-bit) so it cannot be guessed or enumerated.

  • :credential_configurations_supported - map of OID4VCI credential configuration identifiers to the configuration metadata advertised by the Credential Issuer Metadata endpoint. Required when credential issuance is mounted.

  • :federation_authority_hints - non-empty list of superior OpenID Federation entity identifiers to include in this entity's signed Entity Configuration. Optional; the claim is omitted when unset.

  • :federation_entity_metadata - map of OpenID Federation entity-type identifiers to their metadata. Optional; the claim is omitted from the signed Entity Configuration when unset.

  • :status_list_store - module implementing Attesto.StatusListStore, the allocated-index storage for IETF Token Status List (RFC-to-be) credential-status lists. Required when the host mounts the status_list: true route; the endpoint answers 404 for a list it cannot resolve through this store.

  • :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.

  • :notify_ciba_user - (auth_req_id, request, subject -> :ok | {:error, reason}). Starts the out-of-band user-authentication step for a CIBA request. It runs asynchronously after the request is persisted, so the §7.3 acknowledgement never waits for the notification. Only :ok is a successful dispatch; an {:error, reason}, exception, or any other return is logged as a failure and the authentication request remains pending.

  • :client_public? - (client -> boolean()). Returns whether a client may authenticate without a secret and rely on PKCE.

  • :client_native? - (client -> boolean()). Returns whether a client is an installed native application (RFC 8252 / BCP 212). Defaults to false when unset, so a host that does not classify its clients gets no RFC 8252 behavior. A native client always requires PKCE (§8.1) and, when it is also public, may not authenticate at the token endpoint with a secret (§8.4). See also :native_apps.

  • :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.

  • :authorization_code_private_context - (context -> map | nil). Optional trusted issuance callback for host-private authorization state. context contains exactly the authorized :client_id, :subject, and freshly generated authorization-grant provenance :family_id; it contains no request parameters or token secrets. This identifier is the value exposed by :authorization_grant_id_claim when configured, not the separately generated refresh-rotation family identifier. A returned map must be a portable JSON object (Attesto.Claims.portable_json_object?/1: string keys at every level and JSON-safe values) and is limited to 4 KiB once encoded. It rides with the authorization code inside the canonical grant :claims under a reserved namespaced key, and is supplied only as :private_context in the completion callback context. It is stripped from the redeemed core grant before principal construction, so it never reaches an access token, ID Token, refresh token, or token-exchange input. The map is persisted as plaintext JSONB in the authorization row; this feature is not encryption. Store only non-secret identifiers and policy versions such as subject IDs or security epochs, never credentials or token/key material. nil stores nothing. The reserved key belongs to the bundled authorization endpoint. A custom authorization-code issuer or custom reconstruction path accepting request-derived claims MUST reject AttestoPhoenix.AuthorizationCodePrivateContext.claims_key/0 in those claims; reserved?/1 is provided for that check. Exceptions from this callback propagate. When the authorization request came through PAR, its request_uri may already have been claimed before this callback runs, as with other host callbacks. Configure this only together with :authorization_code_completion; missing state remains valid so the host can apply flow-specific fail-closed policy at completion. The reverse skew fails closed on the library's side: a code that DOES carry private context is refused with generic invalid_request unable-to-issue-token response when no :authorization_code_completion callback is configured, so a node whose callback configuration or behaviour module is unavailable cannot issue tokens with the host's completion policy silently skipped. Deploy callback-capable nodes before enabling private context; this check cannot detect an older package version on another node.

  • :authorization_code_completion - (context, continuation -> result). Optional synchronous wrapper around authorization-code completion. It is invoked after the code has been redeemed and before :build_principal or token minting. context contains only the authenticated :client_id, the grant :subject, its :family_id, and the host's :private_context map (or nil); it never contains the authorization code or minted token secrets. The zero-arity continuation performs principal construction, host :build_id_token_claims invocation, access- and ID-token minting, optional logout-session recording, access-token jti recording, optional generation-0 refresh-token insertion, and successful code finalization. A host may run it inside its own database transaction after locking and revalidating the subject's authorization policy.

    This is an initial authorization-code completion hook only. Scope policy and resource-indicator resolution precede it. Refresh, device-code, CIBA, pre-authorized-code, JWT-bearer, client-credentials, token-exchange, and every other grant type bypass the hook. It is not a per-request resource-server or MCP reauthorization mechanism; enforce request-time policy where the access token is used.

    AttestoPhoenix binds the continuation to the callback's process and dynamic scope and permits exactly one invocation; a second, cross-process, or escaped invocation is rejected before token minting or persistence. The callback must return the first invocation's {:ok, response, events} or {:error, oauth_error} result unchanged. This is enforced, not merely documented: a digest and outcome of the continuation's result are recorded and compared without retaining token strings in process state, so a callback that never invokes the continuation cannot pass off a fabricated {:ok, response, events} as a token set, and a callback that substitutes a different success is refused. The {:ok, _} wrapper Repo.transaction/1 places around a committed return is also accepted and unwrapped, because that commit already carried the mint, refresh insertion, and finalization. Only that one wrapper layer is accepted: a nested transaction or Ecto.Multi container must be unwrapped by the host so the callback returns the continuation's exact result. A success wrapper around a failed continuation result is unwrapped to preserve the wire error, but emits a static warning because earlier continuation writes may have committed unless the callback rolled the transaction back. If the continuation raises, throws, or exits before returning and the callback catches that termination, an OAuth error is accepted with a static warning that partial writes may have occurred; a substituted success is refused. The callback is trusted same-process host code: the private process entry is a correctness guard against accidental interference and contract mistakes, not a sandbox against a hostile callback that deliberately inspects or mutates its own process dictionary. Host exception and crash formatters may render callback arguments, including :private_context, which is another reason that map must never contain secrets. If the continuation returns an error inside a transaction, the callback must roll that transaction back rather than commit the normal error tuple. If the continuation succeeds and the host transaction rolls back, the callback MUST return an error and MUST NOT return the captured success result. The library cannot distinguish a rollback from a commit after the callback returns; returning that success would serve a response whose writes were rolled back. Rollback covers only stores using the same Ecto Repo and enclosing transaction as the callback. Stores using another Repo, an independently committed transaction, or an external service remain outside this boundary. A callback may decline to continue with {:error, reason}; an AttestoPhoenix.OAuthError passes through, while any other failure is rendered as a generic token-issuance error without logging the reason. If the callback returns an error, raises, throws, or exits after the continuation succeeds, a transaction may already have committed code finalization; the failed client retry can then be detected as code reuse and revoke that response's access token and the refresh-token family descended from that redemption, forcing a new authorization flow without affecting unrelated authorization grants. Such error returns are logged with this consequence. Exceptions, throws, and exits get the same static, secret-free log before being re-raised with their original stacktrace; the reason and callback context are never logged. The code has already been claimed before this callback runs, so refusal, malformed persisted private context, a missing callback for a context-bearing code, or any callback failure before the continuation succeeds leaves it spent but unfinalized. Audit events follow the final returned result: a substituted error does not emit the continuation's success events, and issue/2 returns the normal :token_denied event for that error. Persisted or external side effects from a committed continuation remain the host's responsibility to reconcile. The spent row has consumed_at set and consumed_success: false, with no refresh family. When unset, the continuation runs directly, preserving existing behavior.

  • :code_store - module implementing Attesto.CodeStore.

  • :refresh_store - module implementing Attesto.RefreshStore. AttestoPhoenix.Store.EctoRefreshStore with a non-zero rotation grace also requires config :attesto_phoenix, :refresh_successor_secret to be a stable secret of at least 32 bytes. The config builder rejects that combination when the secret is missing or invalid.

  • :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).

  • :consent_grant_store - module implementing AttestoPhoenix.ConsentGrantStore, the single-use request-bound consent primitive (RFC 6749 §4.1.1). The host consent screen mints a grant when the resource owner authorizes; the host's :consent callback consumes it before a code is issued, so one consent click cannot approve a different client/redirect/scope/challenge. The library ships the Ecto-backed AttestoPhoenix.Store.EctoConsentGrantStore; there is no default, because the library never renders a consent screen — a host wires this only when it adopts the consent primitive. Read it back with consent_grant_store/1.

  • :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. When unset, defaults to every implemented grant, including enabled optional grants; when set, the list is exact, including []. Include the exact CIBA, device, JWT-bearer, or pre-authorized-code URN when enabling one of those features with an explicit catalog. 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, the default is the exact four methods client_secret_basic, client_secret_post, private_key_jwt, and none. Add tls_client_auth and/or self_signed_tls_client_auth explicitly, together with the required mTLS callbacks, when those methods are retained. Any non-nil list, including [], is exact, except that attest_jwt_client_auth is omitted when trusted Wallet Provider keys are absent.

  • :trusted_wallet_provider_jwks - trusted Wallet Provider public keys for attest_jwt_client_auth, as an RFC 7517 JWK Set, a single public JWK map, or a list of public JWK maps. The method is disabled and omitted from discovery metadata when this is unset.

  • :key_attestation_trusted_jwks - trusted keys for verifying a key_attestation header carried in a credential proof (OID4VCI key attestation), same shapes as :trusted_wallet_provider_jwks. When unset, a present key_attestation header is not inspected.

  • :require_key_attestation - when true, the credential endpoint rejects a proof that carries no key_attestation header (HAIP). Only meaningful with :key_attestation_trusted_jwks; defaults to false.

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, legacy EdDSA over Ed25519, and explicit Ed25519). A non-FAPI deployment can widen it; verification and the advertised metadata stay in lockstep because both read this one value.

  • :client_assertion_audiences - the aud values a private_key_jwt client assertion may carry (RFC 7523 §3), as a list or a one-arity function of the config. Defaults to [issuer, token_endpoint_url].

    Both are accepted by default because the profiles disagree: FAPI 2.0 Security Profile Final §5.3.2.1 requires the issuer identifier, while FAPI-CIBA ID1 audiences a token-endpoint assertion to the token endpoint URL. A deployment certifying to only one of them can narrow this to [config.issuer] and refuse the other.

    Narrowing does not add much: both values identify THIS server, so accepting either does not let an assertion minted for a different authorization server be replayed here — which is what RFC 7523's audience restriction is for. It is a conformance knob, not a security one.

  • :client_auth_enforce_fapi_alg_policy - additionally enforce FAPI's RSA modulus and Edwards-curve restrictions for private_key_jwt. When unset, this defaults to true if :client_auth_signing_algs is omitted and to false if the host supplies an explicit algorithm list. Set it to true when narrowing the FAPI allowlist; an enforced list must remain a subset of Attesto.SigningAlg.fapi_algs/0. Set it to false only for an explicit non-FAPI policy.

  • :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. Supplying an explicit accepted_algs list selects non-FAPI key compatibility unless the policy's enforce_fapi_alg_policy field is true; the named FAPI policy sets that field explicitly, so narrowing its list keeps the key gate enabled.

  • :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"]; add "body" only for a resource server that intentionally accepts RFC 6750 §2.2 form-body access_token credentials and wants to advertise that method.

  • :authorization_endpoint - absolute HTTPS authorization endpoint URL to advertise in OpenID Provider Metadata (RFC 6749 §3.1 / OpenID Connect Discovery §3). attesto_routes/1 mounts the generic controller; the host supplies resource-owner authentication and consent through callbacks. It defaults to the URL derived from :issuer and :authorize_path.

  • :userinfo_endpoint - absolute HTTPS UserInfo URL to advertise in OpenID Provider Metadata (OpenID Connect Core §5.3), :derived, or nil. attesto_routes/1 mounts the generic controller by default and the host supplies claim values through :build_userinfo_claims. A URL is an authoritative host declaration and remains advertised even when the host replaces the bundled route at the same path. :derived resolves through userinfo_endpoint_url/1; when the macro's local UserInfo route is disabled, only that derived value is eligible for suppression at the removed route-equivalent path. nil omits the member.

  • :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 incoming endpoint requests. Default true. This transport gate never relaxes the standards-required HTTPS issuer or advertised endpoint validation.

  • :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. Must be a positive integer no greater than 2_147_483_647. 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. Must be a non-negative integer no greater than :refresh_token_ttl. 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.14.2; FAPI 2.0 Security Profile §5.3.2.1). With the bundled Ecto refresh store, a non-zero value requires the application-wide :refresh_successor_secret described under :refresh_store, a positive :sweep_interval_ms, and supervision of the packaged sweeper or an equivalent cleanup process.

  • :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.

  • :mtls_endpoint_aliases - RFC 8705 §5 endpoint-name-to-HTTPS-URL map for a listener that requests client certificates separately from conventional endpoints. Omitted by default.

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

  • :registration_default_scope - the scope assigned to a dynamically registered client (RFC 7591 §2) when its request omits scope, echoed back in the §3.2.1 response. :scopes_supported assigns the full catalog; a list assigns that explicit subset (validated against :scopes_supported at boot). Default nil - a scopeless registration stays scopeless (fail-closed). Setting this lets a scopeless DCR client (e.g. an MCP/agent client) register with a usable scope without each host reinventing it.

  • :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 as a two-argument function, {module, function}, or {module, function, extra_args} callback. DPoP defaults to the single-node ETS replay cache. CIBA configurations that require signed authentication requests must set this explicitly so request-JWT replay protection cannot be omitted or left unsupervised. An optional signed request is rejected at runtime when this callback is nil.

  • :nonce_store - Attesto.DPoP.NonceStore implementation. No store is selected by default. A capable store is required when :dpop_nonce_required is true; use Attesto.DPoP.NonceStore.ETS on one node or AttestoPhoenix.Store.EctoNonceStore across nodes.

  • :presentation_session_store - module implementing Attesto.PresentationSessionStore for verifier-side OID4VP request state. Required when the host mounts the presentation: true routes or calls AttestoPhoenix.Verifier.

  • :verifier_client_id - verifier identifier placed in the presentation request and used as the holder Key Binding JWT audience. Required when creating verifier presentation requests with the default or "redirect_uri" client-id scheme.

  • :verifier_client_id_scheme - OID4VP verifier client-id scheme. nil (the default) and "redirect_uri" retain the configured :verifier_client_id; "x509_san_dns" derives the identifier from :verifier_dns.

  • :verifier_x5c - verifier certificate chain as DER binaries, leaf first. Required when :verifier_client_id_scheme is "x509_san_dns".

  • :verifier_dns - dNSName advertised by the verifier. Required when :verifier_client_id_scheme is "x509_san_dns".

  • :presentation_response_mode - OID4VP response mode advertised to wallets. Defaults to "direct_post"; set to "direct_post.jwt" to require an encrypted authorization response. That mode also requires a usable :verifier_encryption_keystore when a presentation request is created.

  • :sweep_interval_ms - interval for AttestoPhoenix.Store.Sweeper. start_link/1 rejects an unset or non-positive value. The installer adds the supervised process automatically; manual Ecto configurations must add it after the repo when positive refresh retry grace is enabled. Configuration validates the interval but cannot prove that the host has actually supervised the process.

  • :schema_prefix - optional PostgreSQL schema selected by Ecto's prefix: option for every generated table and index. It must be nil or a non-empty, lowercase ASCII PostgreSQL schema identifier containing only letters, digits, and underscores, beginning with a letter or underscore and no longer than 63 bytes. The 2.x :table_prefix option is rejected; it did not identify one runtime layout: generated migrations could use literal names in public, while most stores queried canonical public tables and only the CIBA store and sweeper used it as an Ecto schema prefix. Inventory an existing database before selecting this value.

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. It does not relocate the router macro's discovery or JWKS routes. Those discovery routes are the fixed origin-issuer forms; see AttestoPhoenix.Router for the path-bearing issuer boundary.
  • :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 bundled router macro mounts fixed /oauth/* tails and takes the path before that segment as its :prefix; hosts that use an explicit endpoint override must mount a matching route themselves.

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

Types

The host-provided values used to issue one JWT VC.

The host-provided values used to issue one mdoc credential.

The host-provided values used to issue one SD-JWT VC.

t()

Functions

The set of resource identifiers this authorization server will mint a token for, for client (RFC 8707 §2.2).

Resolve the authenticate_resource_owner callback. See resolve_callback/2.

Returns the configured authorization-code completion callback, or nil.

Returns the configured authorization-code private-context callback, or nil.

The configured authorization-grant ID access-token claim, or nil when disabled.

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.

Absolute URL of the CIBA backchannel authentication endpoint (advertised as backchannel_authentication_endpoint, CIBA Core §4).

The resolved request path of the CIBA backchannel authentication endpoint (CIBA Core §7).

The module that POSTs a logout_token to a Relying Party's backchannel_logout_uri.

Returns true iff the OP includes sid in its logout tokens (advertised as backchannel_logout_session_supported, Back-Channel Logout 1.0 §2.1). attesto always asserts sid when the session supplies one, so this tracks backchannel_logout_supported?/1.

Returns true iff Back-Channel Logout is supported — logout is enabled AND a :logout_session_store is wired (advertised as backchannel_logout_supported, Back-Channel Logout 1.0 §2.1).

The name of the JavaScript-readable OP browser-state cookie (Session Management 1.0 §3.2). The check_session_iframe script reads it, so it is set without HttpOnly (and with SameSite=None; Secure, since the iframe is embedded cross-site).

The OP browser-state cookie lifetime, in seconds.

The OP-only HMAC key for the browser-state value (Session Management 1.0 §3.2). It makes the value OP-owned (an injected/forged cookie cannot verify) and login-bound (a re-auth / account switch rotates it). Required when session management is enabled — validated at build time by new/1.

Invokes the host's :build_credential callback for the authenticated subject, requested credential configuration, and holder public JWK.

Returns the configured OID4VCI credential builder callback, or nil.

Invokes the host's :build_deferred_credential callback for the authenticated subject and the transaction id the wallet is polling.

Returns the configured OID4VCI deferred-credential builder callback, or nil.

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.

The configured Attesto.CNonceStore module, or nil.

Absolute URL of the check-session iframe (advertised as check_session_iframe, Session Management 1.0 §3.3).

The resolved request path of the check-session iframe (Session Management 1.0 §3.3).

The merged, defaulted OpenID Connect CIBA options.

The advertised + enforced CIBA modes (:poll/:ping; :push is rejected at boot).

Returns true iff OpenID Connect CIBA is enabled (ciba: [enabled: true]). When enabled and grant_types_supported is nil, urn:openid:params:grant-type:ciba is added to grant_types_supported/1 and the backchannel_authentication_endpoint + CIBA capability metadata are advertised. An explicit grant catalog must include the CIBA URN. The host MUST ALSO pass ciba: true to attesto_routes/1 to mount the endpoint.

The module implementing AttestoPhoenix.CIBAPing for ping-mode delivery.

The configured Attesto.CIBAStore module, or nil.

The aud values a private_key_jwt client assertion may carry (RFC 7523 §3).

The Relying Party's backchannel_logout_session_required (Back-Channel Logout 1.0 §2.2): whether its logout token MUST carry sid. Defaults to false.

Resolve the client_backchannel_logout_session_required callback. See resolve_callback/2.

The Relying Party's registered backchannel_logout_uri (Back-Channel Logout 1.0 §2.2), or nil when the client is not back-channel-logout capable (so no logout session is recorded and no token is fanned out to it).

Resolve the client_backchannel_logout_uri callback. See resolve_callback/2.

The client's registered CIBA metadata (CIBA Core §4), resolved from the host :client_ciba_registration callback (or the AttestoPhoenix.ClientStore behaviour), as a map with :token_delivery_mode (:poll | :ping | :push), :client_notification_endpoint, :request_signing_alg, and :user_code_parameter. A client the host does not register for CIBA resolves to %{} (the core then treats it as unauthorized_client).

Resolve the client_ciba_registration callback. See resolve_callback/2.

The Relying Party's frontchannel_logout_session_required (Front-Channel Logout 1.0 §2): whether the rendered logout URI must carry iss and sid query parameters. Defaults to false.

Resolve the client_frontchannel_logout_session_required callback. See resolve_callback/2.

The Relying Party's registered frontchannel_logout_uri (Front-Channel Logout 1.0 §2), or nil when the client is not front-channel-logout capable (so no iframe is rendered for it on the logout page).

Resolve the client_frontchannel_logout_uri 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 host identifier for an opaque client.

Resolve the client_jwks callback. See resolve_callback/2.

Resolve the client_mtls_metadata callback. See resolve_callback/2.

Resolve the client_native? callback. See resolve_callback/2.

The Relying Party's registered post_logout_redirect_uris (RP-Initiated Logout 1.0 §2): the :client_post_logout_redirect_uris callback's result, or [] when the host wires none (so an unvalidatable post_logout_redirect_uri is always refused).

Resolve the client_post_logout_redirect_uris 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.

Returns the configured single-use consent-grant store module, or nil.

The configured OID4VCI credential-configuration catalog, or nil.

Absolute URL of the OID4VCI credential endpoint.

The resolved request path of the OID4VCI credential-offer endpoint.

The resolved request path of the OID4VCI credential endpoint.

Absolute URL of the OID4VCI deferred-credential endpoint.

The resolved request path of the OID4VCI deferred-credential endpoint.

The merged, defaulted RFC 8628 device-authorization options.

Returns true iff the RFC 8628 device authorization grant is enabled (device_authorization: [enabled: true]). When enabled, device_code is added to grant_types_supported/1 only when that setting is nil, and the device_authorization_endpoint is advertised. An explicit grant catalog must include urn:ietf:params:oauth:grant-type:device_code.

Absolute URL of the device-authorization endpoint (advertised as device_authorization_endpoint, RFC 8628 §4).

The resolved request path of the device-authorization endpoint (RFC 8628).

The configured Attesto.DeviceCodeStore module, or nil.

Absolute URL of the device-verification page.

The resolved request path of the device-verification page (RFC 8628 §3.3).

The RFC 8628 §3.2 verification URI shown to the user: the configured device_authorization: [verification_uri: ...] override, otherwise the issuer-derived device-verification endpoint URL.

Returns the configured Ecto repository, raising when it is unset.

Absolute URL of the end-session endpoint (advertised as end_session_endpoint, RP-Initiated Logout 1.0 §2).

The resolved request path of the end-session endpoint (RP-Initiated Logout 1.0).

The OpenID Federation superior entity identifiers, or nil.

The OpenID Federation entity-type metadata map, or nil.

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

Returns true iff the OP passes iss/sid query parameters on the rendered frontchannel_logout_uri (advertised as frontchannel_logout_session_supported, Front-Channel Logout 1.0 §3). attesto always includes both whenever the session's sid is known, so this tracks frontchannel_logout_supported?/1.

Returns true iff Front-Channel Logout is supported — logout is enabled AND a :logout_session_store is wired (advertised as frontchannel_logout_supported, Front-Channel Logout 1.0 §3). The store is what lets the end-session endpoint enumerate the RPs whose frontchannel_logout_uri the logout page must render.

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). This library keeps the JWKS document at its stable root path, 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.

Trusted keys for verifying a key_attestation header in a credential proof.

The configured keystore used for ID-token and authorization-server signing.

Resolve the load_client callback. See resolve_callback/2.

Resolve the load_principal callback. See resolve_callback/2.

The merged, defaulted OpenID Connect logout (RP-Initiated + Back-Channel) options.

Returns true iff OpenID Connect logout is enabled (logout: [enabled: true]). When enabled, the end_session_endpoint is advertised and (with a :logout_session_store wired) Back-Channel Logout is supported. The host MUST ALSO pass logout: true to attesto_routes/1 to mount the endpoint.

The configured Attesto.LogoutSessionStore module (Back-Channel Logout), or nil.

How long a recorded back-channel-logout session lives before it is swept, in seconds.

The redirect-URI matching mode a loopback-capable client gets: RFC 8252 §7.3 port flexibility for the IP literals, widened to the bare localhost name iff the host opted in with native_apps: [loopback_include_localhost: true].

Returns true unless the host has forbidden RFC 8252 §7.3 loopback interface redirection server-wide with native_apps: [loopback_redirect: false].

Returns the merged, defaulted RFC 8252 native-app profile options, so every recognized member (:loopback_redirect, :loopback_include_localhost, :reject_embedded_user_agents) is always present.

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

Absolute URL of the OID4VCI nonce endpoint.

The resolved request path of the OID4VCI nonce endpoint.

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.

Absolute URL of the OID4VP request-object endpoint.

The resolved request path of the OID4VP request-object endpoint.

Absolute URL of the OID4VP direct-post response endpoint.

The OID4VP direct-post response mode advertised to wallets.

The resolved request path of the OID4VP direct-post response endpoint.

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.

The scope a dynamically registered client is assigned when its registration request omits scope (RFC 7591 §2: the authorization server MAY register a default scope). Resolves the :registration_default_scope setting to a concrete list

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.

Returns true iff the authorization endpoint should refuse requests that appear to come from an embedded user agent (RFC 8252 §8.12).

Return the bounded operation's request config, or nil outside one.

Whether a credential proof MUST carry a verified key_attestation header.

Resolves the validated config from the library's configured :otp_app.

Resolves the validated config installed on a request connection.

Resolve a configured callback by its flat key.

Resolve the resolve_jwt_bearer_subject callback. See resolve_callback/2.

Returns the merged, defaulted RFC 8707 Resource Indicators options (:allowed_resources, :allowed_resources_for).

Selects the RFC 9728 protected-resource metadata URL for conn.

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.

Resolve the public PostgreSQL schema prefix from a validated config.

The merged, defaulted OpenID Connect Session Management 1.0 options.

Returns true iff OpenID Connect Session Management 1.0 is enabled (session_management: [enabled: true]). When enabled, the discovery document advertises check_session_iframe, the authorization endpoint returns session_state on authorization responses, and the OP browser-state cookie is maintained. The host MUST ALSO pass session_management: true to attesto_routes/1 to mount the iframe endpoint.

The configured default audience plus static RFC 8707 resource identifiers.

Absolute URL of the Token Status List endpoint.

The resolved request path of the Token Status List endpoint.

The configured Attesto.StatusListStore module, or nil.

Resolve the Ecto schema prefix for the current operation.

Resolve the prefix from an explicit config or connection.

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

The client-authentication methods accepted at the token endpoint.

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.

Trusted Wallet Provider keys for attestation-based client authentication.

Resolve the unregister_client callback. See resolve_callback/2.

Absolute URL of the UserInfo endpoint: the issuer merged with userinfo_path/1. This is the URL selected by userinfo_endpoint: :derived; an explicit URL or nil remains authoritative in Provider Metadata.

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

The keystore used to sign issued Verifiable Credentials; defaults to :keystore. Configure a separate EC/ES256 keystore here to issue ES256-signed credentials (e.g. for HAIP) while ID tokens keep their own signing key.

Returns the PEM used to sign issued Verifiable Credentials.

The VC signing key's X.509 certificate chain, or nil.

The verifier client identifier used as the OID4VP presentation audience.

The OID4VP verifier client-id scheme, or nil for the default behavior.

The dNSName advertised by an x509_san_dns verifier.

The dedicated EC P-256 keystore for encrypted OID4VP responses, or nil.

The verifier certificate chain as DER binaries, leaf first.

Resolve the verify_client_secret callback. See resolve_callback/2.

Run a conn-free callback with config as the request-local configuration.

Types

callback()

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

credential_result()

jwt_vc_credential_result()

@type jwt_vc_credential_result() :: %{
  :credential_type => String.t(),
  :claims => map(),
  optional(:valid_from) => integer(),
  optional(:valid_until) => integer()
}

The host-provided values used to issue one JWT VC.

mdoc_credential_result()

@type mdoc_credential_result() :: %{
  :namespaces => %{required(String.t()) => %{required(String.t()) => term()}},
  optional(:doc_type) => String.t(),
  optional(:valid_from) => integer(),
  optional(:valid_until) => integer()
}

The host-provided values used to issue one mdoc credential.

sd_jwt_credential_result()

@type sd_jwt_credential_result() :: %{
  :vct => String.t(),
  :claims => map(),
  optional(:valid_from) => integer(),
  optional(:valid_until) => integer()
}

The host-provided values used to issue one SD-JWT VC.

t()

@type t() :: %AttestoPhoenix.Config{
  access_token_ttl: pos_integer(),
  acr_values_supported: [String.t()],
  audience: String.t() | [String.t()] | nil,
  authenticate_ciba_user: callback() | nil,
  authenticate_device_user: term(),
  authenticate_resource_owner: callback() | nil,
  authorization_code_completion: callback() | nil,
  authorization_code_private_context: callback() | nil,
  authorization_code_ttl: pos_integer(),
  authorization_endpoint: String.t() | nil,
  authorization_grant_id_claim: String.t() | nil,
  authorization_response_iss: boolean(),
  authorize_path: String.t() | nil,
  authorize_scope: callback() | nil,
  backchannel_authentication_path: String.t() | nil,
  basic_realm: String.t(),
  bearer_methods_supported: [String.t()],
  build_credential: callback() | nil,
  build_deferred_credential: callback() | nil,
  build_id_token_claims: callback() | nil,
  build_principal: callback() | nil,
  build_userinfo_claims: callback() | nil,
  c_nonce_store: module() | nil,
  cert_der: callback() | nil,
  check_session_path: String.t() | nil,
  ciba: keyword(),
  ciba_ping_http_client: module(),
  ciba_store: module() | nil,
  claims_parameter_supported: boolean(),
  claims_provider: module() | nil,
  claims_supported: [String.t()],
  client_assertion_audiences: [String.t()] | (t() -> [String.t()]) | nil,
  client_auth_enforce_fapi_alg_policy: boolean() | nil,
  client_auth_signing_algs: [String.t()] | nil,
  client_backchannel_logout_session_required: callback() | nil,
  client_backchannel_logout_uri: callback() | nil,
  client_certificate_chain_validated?: callback() | nil,
  client_ciba_registration: callback() | nil,
  client_frontchannel_logout_session_required: callback() | nil,
  client_frontchannel_logout_uri: callback() | nil,
  client_grant_types: callback() | nil,
  client_id: callback() | nil,
  client_id_metadata: keyword(),
  client_jwks: callback() | nil,
  client_mtls_metadata: callback() | nil,
  client_native?: callback() | nil,
  client_post_logout_redirect_uris: 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_grant_store: module() | nil,
  consent_policy: module() | nil,
  credential_configurations_supported: map() | nil,
  credential_offer_store: module() | nil,
  device_authorization: term(),
  device_authorization_path: term(),
  device_code_store: module() | nil,
  device_verification_path: term(),
  dpop_enabled: boolean(),
  dpop_nonce_required: boolean(),
  end_session_path: term(),
  event_sink: module() | nil,
  federation_authority_hints: [String.t()] | nil,
  federation_entity_metadata: map() | nil,
  forwarded_cert_der: callback() | 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(),
  key_attestation_trusted_jwks: map() | [map()] | nil,
  keystore: module(),
  load_client: callback(),
  load_principal: callback(),
  logout: keyword(),
  logout_session_store: term(),
  mtls_enabled: boolean(),
  mtls_endpoint_aliases: %{optional(String.t()) => String.t()} | nil,
  native_apps: keyword(),
  no_store: callback() | nil,
  nonce_store: module() | nil,
  notify_ciba_user: callback() | nil,
  oauth_path_prefix: String.t(),
  on_event: callback() | nil,
  par_path: String.t() | nil,
  par_store: module() | nil,
  par_ttl: pos_integer(),
  pre_authorized_code_store: module() | nil,
  presentation_response_mode: String.t(),
  presentation_session_store: module() | nil,
  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_default_scope: [String.t()] | :scopes_supported | nil,
  registration_enabled: boolean(),
  registration_path: String.t() | nil,
  render_device_verification: term(),
  render_logged_out: callback() | nil,
  replay_check: callback() | nil,
  repo: module(),
  request_object_policy: Attesto.RequestObject.Policy.t() | nil,
  require_https: boolean(),
  require_key_attestation: boolean() | nil,
  require_nonce: boolean(),
  require_pkce: boolean(),
  require_pushed_authorization_requests: boolean(),
  resolve_jwt_bearer_subject: callback() | nil,
  resource_indicators: keyword(),
  resource_metadata: String.t() | nil,
  resource_metadata_resolver: callback() | nil,
  revocation_path: String.t() | nil,
  schema_prefix: String.t() | nil,
  scope_policy: module() | nil,
  scopes_supported: [String.t()],
  send_error: callback() | nil,
  session_management: keyword(),
  status_list_store: module() | nil,
  sweep_interval_ms: pos_integer() | nil,
  terminate_session: callback() | nil,
  token_endpoint_auth_methods_supported: [String.t()] | nil,
  token_path: String.t() | nil,
  trusted_proxies: [String.t()],
  trusted_wallet_provider_jwks: map() | [map()] | nil,
  ui_locales_supported: [String.t()],
  unregister_client: callback() | nil,
  userinfo_endpoint: String.t() | :derived | nil,
  userinfo_path: String.t() | nil,
  vc_keystore: module() | nil,
  verifier_client_id: String.t() | nil,
  verifier_client_id_scheme: String.t() | nil,
  verifier_dns: String.t() | nil,
  verifier_encryption_keystore: module() | nil,
  verifier_x5c: [binary()] | nil,
  verify_client_secret: callback(),
  www_authenticate: callback() | nil
}

Functions

allowed_resources(config, client)

@spec allowed_resources(t(), term()) :: [String.t()]

The set of resource identifiers this authorization server will mint a token for, for client (RFC 8707 §2.2).

Composes the server's own :audience (always served), the static resource_indicators: [allowed_resources: [...]] list, and the per-client :allowed_resources_for callback's result. A requested resource is honored only when it appears here; anything else is invalid_target.

authenticate_resource_owner_fun(config)

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

Resolve the authenticate_resource_owner callback. See resolve_callback/2.

authorization_code_completion_fun(config)

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

Returns the configured authorization-code completion callback, or nil.

See the :authorization_code_completion option. When nil, the token endpoint runs the completion continuation directly. The continuation closure captures completion state, including the plaintext authorization code; trusted host code MUST NOT inspect or dump its function environment.

authorization_code_private_context_fun(config)

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

Returns the configured authorization-code private-context callback, or nil.

See the :authorization_code_private_context option. The value the callback returns rides with the code inside the canonical grant :claims and is surfaced only to :authorization_code_completion.

authorization_grant_id_claim(config)

@spec authorization_grant_id_claim(t()) :: String.t() | nil

The configured authorization-grant ID access-token claim, or nil when disabled.

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.

backchannel_authentication_endpoint_url(config)

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

Absolute URL of the CIBA backchannel authentication endpoint (advertised as backchannel_authentication_endpoint, CIBA Core §4).

backchannel_authentication_path(config)

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

The resolved request path of the CIBA backchannel authentication endpoint (CIBA Core §7).

backchannel_logout_http(config)

@spec backchannel_logout_http(t()) :: module()

The module that POSTs a logout_token to a Relying Party's backchannel_logout_uri.

backchannel_logout_session_supported?(config)

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

Returns true iff the OP includes sid in its logout tokens (advertised as backchannel_logout_session_supported, Back-Channel Logout 1.0 §2.1). attesto always asserts sid when the session supplies one, so this tracks backchannel_logout_supported?/1.

backchannel_logout_supported?(config)

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

Returns true iff Back-Channel Logout is supported — logout is enabled AND a :logout_session_store is wired (advertised as backchannel_logout_supported, Back-Channel Logout 1.0 §2.1).

browser_state_cookie(config)

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

The name of the JavaScript-readable OP browser-state cookie (Session Management 1.0 §3.2). The check_session_iframe script reads it, so it is set without HttpOnly (and with SameSite=None; Secure, since the iframe is embedded cross-site).

browser_state_secret(config)

@spec browser_state_secret(t()) :: binary() | nil

The OP-only HMAC key for the browser-state value (Session Management 1.0 §3.2). It makes the value OP-owned (an injected/forged cookie cannot verify) and login-bound (a re-auth / account switch rotates it). Required when session management is enabled — validated at build time by new/1.

build_credential(config, subject, credential_configuration_id, holder_jwk)

@spec build_credential(t(), String.t(), String.t(), map()) ::
  {:ok, credential_result()} | {:error, term()}

Invokes the host's :build_credential callback for the authenticated subject, requested credential configuration, and holder public JWK.

Raises ArgumentError when the callback is not configured, so a mounted Credential endpoint cannot silently issue an empty credential.

build_credential_fun(config)

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

Returns the configured OID4VCI credential builder callback, or nil.

build_deferred_credential(config, subject, transaction_id)

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

Invokes the host's :build_deferred_credential callback for the authenticated subject and the transaction id the wallet is polling.

Raises ArgumentError when the callback is not configured, so a Deferred Credential endpoint request cannot silently issue an empty credential.

build_deferred_credential_fun(config)

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

Returns the configured OID4VCI deferred-credential builder callback, or nil.

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.

c_nonce_store(config)

@spec c_nonce_store(t()) :: module() | nil

The configured Attesto.CNonceStore module, or nil.

check_session_iframe_url(config)

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

Absolute URL of the check-session iframe (advertised as check_session_iframe, Session Management 1.0 §3.3).

check_session_path(config)

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

The resolved request path of the check-session iframe (Session Management 1.0 §3.3).

ciba(config)

@spec ciba(t()) :: keyword()

The merged, defaulted OpenID Connect CIBA options.

ciba_delivery_modes(config)

@spec ciba_delivery_modes(t()) :: [:poll | :ping]

The advertised + enforced CIBA modes (:poll/:ping; :push is rejected at boot).

ciba_enabled?(config)

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

Returns true iff OpenID Connect CIBA is enabled (ciba: [enabled: true]). When enabled and grant_types_supported is nil, urn:openid:params:grant-type:ciba is added to grant_types_supported/1 and the backchannel_authentication_endpoint + CIBA capability metadata are advertised. An explicit grant catalog must include the CIBA URN. The host MUST ALSO pass ciba: true to attesto_routes/1 to mount the endpoint.

ciba_ping_http_client(config)

@spec ciba_ping_http_client(t()) :: module()

The module implementing AttestoPhoenix.CIBAPing for ping-mode delivery.

ciba_store(config)

@spec ciba_store(t()) :: module() | nil

The configured Attesto.CIBAStore module, or nil.

client_assertion_audiences(config)

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

The aud values a private_key_jwt client assertion may carry (RFC 7523 §3).

Defaults to the issuer identifier and the token endpoint URL, because the profiles disagree about which one is required — see :client_assertion_audiences in the moduledoc. A deployment certifying to a single profile can narrow it.

client_backchannel_logout_session_required(config, client)

@spec client_backchannel_logout_session_required(t(), term()) :: boolean()

The Relying Party's backchannel_logout_session_required (Back-Channel Logout 1.0 §2.2): whether its logout token MUST carry sid. Defaults to false.

client_backchannel_logout_session_required_fun(config)

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

Resolve the client_backchannel_logout_session_required callback. See resolve_callback/2.

client_backchannel_logout_uri(config, client)

@spec client_backchannel_logout_uri(t(), term()) :: String.t() | nil

The Relying Party's registered backchannel_logout_uri (Back-Channel Logout 1.0 §2.2), or nil when the client is not back-channel-logout capable (so no logout session is recorded and no token is fanned out to it).

The URI is also fail-closed against server-side request forgery: the OP POSTs a logout_token to it, so a non-https URL, one carrying userinfo/a fragment, or one whose host is a loopback / private / link-local / unique-local literal (e.g. 127.0.0.1, 10.x, 169.254.169.254, localhost) is treated as absent. A registered URL that resolves to an internal address only via DNS is a residual risk the host's egress controls own.

client_backchannel_logout_uri_fun(config)

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

Resolve the client_backchannel_logout_uri callback. See resolve_callback/2.

client_ciba_registration(config, client)

@spec client_ciba_registration(t(), term()) :: map()

The client's registered CIBA metadata (CIBA Core §4), resolved from the host :client_ciba_registration callback (or the AttestoPhoenix.ClientStore behaviour), as a map with :token_delivery_mode (:poll | :ping | :push), :client_notification_endpoint, :request_signing_alg, and :user_code_parameter. A client the host does not register for CIBA resolves to %{} (the core then treats it as unauthorized_client).

client_ciba_registration_fun(config)

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

Resolve the client_ciba_registration callback. See resolve_callback/2.

client_frontchannel_logout_session_required(config, client)

@spec client_frontchannel_logout_session_required(t(), term()) :: boolean()

The Relying Party's frontchannel_logout_session_required (Front-Channel Logout 1.0 §2): whether the rendered logout URI must carry iss and sid query parameters. Defaults to false.

client_frontchannel_logout_session_required_fun(config)

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

Resolve the client_frontchannel_logout_session_required callback. See resolve_callback/2.

client_frontchannel_logout_uri(config, client)

@spec client_frontchannel_logout_uri(t(), term()) :: String.t() | nil

The Relying Party's registered frontchannel_logout_uri (Front-Channel Logout 1.0 §2), or nil when the client is not front-channel-logout capable (so no iframe is rendered for it on the logout page).

The URI is rendered as an <iframe src> on the OP's HTTPS logout page, so a non-https URL (which browsers would block as mixed content, and which §2 only permits for confidential clients), one carrying userinfo, or one with no host is treated as absent (fail closed).

client_frontchannel_logout_uri_fun(config)

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

Resolve the client_frontchannel_logout_uri 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_identifier(config, client, absent_default \\ nil)

@spec client_identifier(t(), term(), term()) :: String.t() | term()

Resolve the host identifier for an opaque client.

absent_default is used only when no :client_id callback is configured. Once configured, the callback must honor its non-empty-string contract; invalid results are integration faults and never fall through to a credential-carried identifier.

client_jwks_fun(config)

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

Resolve the client_jwks callback. See resolve_callback/2.

client_mtls_metadata_fun(config)

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

Resolve the client_mtls_metadata callback. See resolve_callback/2.

client_native_fun(config)

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

Resolve the client_native? callback. See resolve_callback/2.

client_post_logout_redirect_uris(config, client)

@spec client_post_logout_redirect_uris(t(), term()) :: [String.t()]

The Relying Party's registered post_logout_redirect_uris (RP-Initiated Logout 1.0 §2): the :client_post_logout_redirect_uris callback's result, or [] when the host wires none (so an unvalidatable post_logout_redirect_uri is always refused).

client_post_logout_redirect_uris_fun(config)

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

Resolve the client_post_logout_redirect_uris 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()) ::
  {:ok, term()} | {:error, :not_found | :revoked}

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.

credential_configurations_supported(config)

@spec credential_configurations_supported(t()) :: map() | nil

The configured OID4VCI credential-configuration catalog, or nil.

credential_endpoint_url(config)

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

Absolute URL of the OID4VCI credential endpoint.

credential_offer_path(config)

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

The resolved request path of the OID4VCI credential-offer endpoint.

credential_offer_store(config)

@spec credential_offer_store(t()) :: module() | nil

The configured Attesto.CredentialOfferStore module, or nil.

credential_path(config)

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

The resolved request path of the OID4VCI credential endpoint.

deferred_credential_endpoint_url(config)

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

Absolute URL of the OID4VCI deferred-credential endpoint.

deferred_credential_path(config)

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

The resolved request path of the OID4VCI deferred-credential endpoint.

device_authorization(config)

@spec device_authorization(t()) :: keyword()

The merged, defaulted RFC 8628 device-authorization options.

device_authorization_enabled?(config)

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

Returns true iff the RFC 8628 device authorization grant is enabled (device_authorization: [enabled: true]). When enabled, device_code is added to grant_types_supported/1 only when that setting is nil, and the device_authorization_endpoint is advertised. An explicit grant catalog must include urn:ietf:params:oauth:grant-type:device_code.

device_authorization_endpoint_url(config)

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

Absolute URL of the device-authorization endpoint (advertised as device_authorization_endpoint, RFC 8628 §4).

device_authorization_path(config)

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

The resolved request path of the device-authorization endpoint (RFC 8628).

device_code_store(config)

@spec device_code_store(t()) :: module() | nil

The configured Attesto.DeviceCodeStore module, or nil.

device_verification_endpoint_url(config)

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

Absolute URL of the device-verification page.

device_verification_path(config)

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

The resolved request path of the device-verification page (RFC 8628 §3.3).

device_verification_uri(config)

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

The RFC 8628 §3.2 verification URI shown to the user: the configured device_authorization: [verification_uri: ...] override, otherwise the issuer-derived device-verification endpoint URL.

ecto_repo!()

@spec ecto_repo!() :: module()

Returns the configured Ecto repository, raising when it is unset.

A validated request-local config always wins. Without a request config, the host config under the configured :otp_app is used. A package-level :repo is retained only for legacy deployments that have no :otp_app pointer; it is never used to rescue a missing or malformed request/host config.

missing_message is available for adapters that have a more specific existing error message; the lookup and default failure stay shared.

ecto_repo!(missing_message)

@spec ecto_repo!(String.t()) :: module()

end_session_endpoint_url(config)

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

Absolute URL of the end-session endpoint (advertised as end_session_endpoint, RP-Initiated Logout 1.0 §2).

end_session_path(config)

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

The resolved request path of the end-session endpoint (RP-Initiated Logout 1.0).

federation_authority_hints(config)

@spec federation_authority_hints(t()) :: [String.t()] | nil

The OpenID Federation superior entity identifiers, or nil.

federation_entity_metadata(config)

@spec federation_entity_metadata(t()) :: map() | nil

The OpenID Federation entity-type metadata map, or nil.

from_otp_app(otp_app, key \\ __MODULE__)

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

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

frontchannel_logout_session_supported?(config)

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

Returns true iff the OP passes iss/sid query parameters on the rendered frontchannel_logout_uri (advertised as frontchannel_logout_session_supported, Front-Channel Logout 1.0 §3). attesto always includes both whenever the session's sid is known, so this tracks frontchannel_logout_supported?/1.

frontchannel_logout_supported?(config)

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

Returns true iff Front-Channel Logout is supported — logout is enabled AND a :logout_session_store is wired (advertised as frontchannel_logout_supported, Front-Channel Logout 1.0 §3). The store is what lets the end-session endpoint enumerate the RPs whose frontchannel_logout_uri the logout page must render.

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. When unset, defaults to every grant the token endpoint implements (["authorization_code", "refresh_token", "client_credentials", "urn:ietf:params:oauth:grant-type:token-exchange"]) plus enabled optional-feature grants. When set, the list is exact, including []; include the exact optional-feature URN yourself when the feature is enabled. This same catalog drives discovery, endpoint enforcement, and dynamic registration.

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). This library keeps the JWKS document at its stable root path, 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, :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 and grant_types_supported is nil, urn:ietf:params:oauth:grant-type:jwt-bearer is added to grant_types_supported/1 (so both discovery and the token endpoint honour it). An explicit grant catalog must include that URN when this feature is used.

key_attestation_trusted_jwks(config)

@spec key_attestation_trusted_jwks(t()) :: map() | [map()] | nil

Trusted keys for verifying a key_attestation header in a credential proof.

Returns an RFC 7517 JWK Set, a single public JWK map, a list of public JWK maps, or nil when key-attestation verification is disabled.

keystore(config)

@spec keystore(t()) :: module()

The configured keystore used for ID-token and authorization-server signing.

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.

logout(config)

@spec logout(t()) :: keyword()

The merged, defaulted OpenID Connect logout (RP-Initiated + Back-Channel) options.

logout_enabled?(config)

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

Returns true iff OpenID Connect logout is enabled (logout: [enabled: true]). When enabled, the end_session_endpoint is advertised and (with a :logout_session_store wired) Back-Channel Logout is supported. The host MUST ALSO pass logout: true to attesto_routes/1 to mount the endpoint.

logout_session_store(config)

@spec logout_session_store(t()) :: module() | nil

The configured Attesto.LogoutSessionStore module (Back-Channel Logout), or nil.

logout_session_ttl_seconds(config)

@spec logout_session_ttl_seconds(t()) :: pos_integer()

How long a recorded back-channel-logout session lives before it is swept, in seconds.

native_app_loopback_matching(config)

@spec native_app_loopback_matching(t()) :: Attesto.RedirectURI.matching()

The redirect-URI matching mode a loopback-capable client gets: RFC 8252 §7.3 port flexibility for the IP literals, widened to the bare localhost name iff the host opted in with native_apps: [loopback_include_localhost: true].

This resolves WHICH loopback mode applies, not WHETHER one does - the caller (AttestoPhoenix.AuthorizationServer.RequestPolicy.redirect_uri_matching/2) still gates on the client being native (or a CIMD document declaring a loopback redirect URI) and on native_app_loopback_redirect?/1, and resolves :exact when either gate refuses.

native_app_loopback_redirect?(config)

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

Returns true unless the host has forbidden RFC 8252 §7.3 loopback interface redirection server-wide with native_apps: [loopback_redirect: false].

This is an opt-OUT: §7.3 states the port allowance as a MUST, so it follows from a client being marked native (:client_native?, itself defaulting to false) rather than from a second flag. Even enabled, the exception applies only to a native client and only to http://127.0.0.1/... / http://[::1]/... redirect URIs; see AttestoPhoenix.AuthorizationServer.RequestPolicy.redirect_uri_matching/2 and Attesto.RedirectURI.

native_apps(config)

@spec native_apps(t()) :: keyword()

Returns the merged, defaulted RFC 8252 native-app profile options, so every recognized member (:loopback_redirect, :loopback_include_localhost, :reject_embedded_user_agents) is always present.

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).

nonce_endpoint_url(config)

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

Absolute URL of the OID4VCI nonce endpoint.

nonce_path(config)

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

The resolved request path of the OID4VCI nonce endpoint.

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.

pre_authorized_code_store(config)

@spec pre_authorized_code_store(t()) :: module() | nil

The configured Attesto.PreAuthorizedCodeStore module, or nil.

presentation_request_endpoint_url(config)

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

Absolute URL of the OID4VP request-object endpoint.

presentation_request_path(config)

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

The resolved request path of the OID4VP request-object endpoint.

presentation_response_endpoint_url(config)

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

Absolute URL of the OID4VP direct-post response endpoint.

presentation_response_mode(config)

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

The OID4VP direct-post response mode advertised to wallets.

presentation_response_path(config)

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

The resolved request path of the OID4VP direct-post response endpoint.

presentation_session_store(config)

@spec presentation_session_store(t()) :: module() | nil

The configured Attesto.PresentationSessionStore module, or nil.

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_default_scope(config)

@spec registration_default_scope(t()) :: [String.t()] | nil

The scope a dynamically registered client is assigned when its registration request omits scope (RFC 7591 §2: the authorization server MAY register a default scope). Resolves the :registration_default_scope setting to a concrete list:

  • :scopes_supported — every scope in scopes_supported.
  • a list of scope strings — that explicit default (the registration layer still rejects any member outside scopes_supported).
  • nil (the default) — no defaulting; a scopeless registration stays scopeless (fail-closed).

Returns the resolved list, or nil when no default applies.

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.

reject_embedded_user_agents?(config)

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

Returns true iff the authorization endpoint should refuse requests that appear to come from an embedded user agent (RFC 8252 §8.12).

Off unless the host sets native_apps: [reject_embedded_user_agents: true]. The detection is a heuristic; see AttestoPhoenix.RequestContext.embedded_user_agent?/1.

request_config()

@spec request_config() :: t() | nil

Return the bounded operation's request config, or nil outside one.

require_key_attestation?(config)

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

Whether a credential proof MUST carry a verified key_attestation header.

resolve!()

@spec resolve!() :: t()

Resolves the validated config from the library's configured :otp_app.

This is the shared resolution path for controllers that read the global application configuration. Configure the pointer with config :attesto_phoenix, otp_app: :my_app; the Igniter installer writes it together with the repo used by the bundled Ecto stores.

resolve!(conn)

@spec resolve!(Plug.Conn.t()) :: t()

Resolves the validated config installed on a request connection.

Controllers must use this request-scoped value so a host can select a validated configuration per request. A missing or malformed private value is a pipeline wiring error and fails closed with ArgumentError; this function never falls back to application configuration.

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.

resource_indicators(config)

@spec resource_indicators(t()) :: keyword()

Returns the merged, defaulted RFC 8707 Resource Indicators options (:allowed_resources, :allowed_resources_for).

resource_metadata_url(config, conn, plug_opts \\ [])

@spec resource_metadata_url(t(), Plug.Conn.t(), keyword()) :: String.t() | nil

Selects the RFC 9728 protected-resource metadata URL for conn.

A configured :resource_metadata_resolver is authoritative and may return an absolute HTTPS URL or nil to omit the resource_metadata auth-param for this request. Without a resolver, the static :resource_metadata value is returned unchanged. Invalid resolver results are omitted so a challenge never advertises an unusable or unsafe metadata URI. The resolver runs once per protected-resource request, including requests that authenticate successfully, because the selected URI must be in place before verification renders any challenge. Resolver exceptions are deliberately not rescued: trusted configuration that raises aborts the request - successful ones included - instead of disguising a host failure as an authentication response.

The optional third argument is a plug option list. When it contains :resource_metadata, that explicit value (including nil) is authoritative, is validated by the same rules, and the configured resolver is not invoked. This keeps a per-plug override consistent across every error path.

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.

schema_prefix()

@spec schema_prefix() :: String.t() | nil

Resolve the public PostgreSQL schema prefix from a validated config.

schema_prefix(config)

@spec schema_prefix(t() | Plug.Conn.t()) :: String.t() | nil

session_management(config)

@spec session_management(t()) :: keyword()

The merged, defaulted OpenID Connect Session Management 1.0 options.

session_management_enabled?(config)

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

Returns true iff OpenID Connect Session Management 1.0 is enabled (session_management: [enabled: true]). When enabled, the discovery document advertises check_session_iframe, the authorization endpoint returns session_state on authorization responses, and the OP browser-state cookie is maintained. The host MUST ALSO pass session_management: true to attesto_routes/1 to mount the iframe endpoint.

static_allowed_resources(config)

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

The configured default audience plus static RFC 8707 resource identifiers.

Unlike allowed_resources/2, this does not invoke the per-client issuance callback. It is the safe fallback when no verified original OAuth client is available, including malformed or legacy tokens during introspection.

status_list_endpoint_url(config)

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

Absolute URL of the Token Status List endpoint.

status_list_path(config)

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

The resolved request path of the Token Status List endpoint.

status_list_store(config)

@spec status_list_store(t()) :: module() | nil

The configured Attesto.StatusListStore module, or nil.

table_prefix()

@spec table_prefix() :: String.t() | nil

Resolve the Ecto schema prefix for the current operation.

A request-local config installed by with_request_config/2 wins. Without one, the fallback is the validated host AttestoPhoenix.Config read under the configured :otp_app; this function deliberately does not consult a separate config :attesto_phoenix, :table_prefix key. In 3.0 the public option is :schema_prefix; :table_prefix is reserved for the internal accessor retained for store compatibility.

table_prefix(config)

@spec table_prefix(t() | Plug.Conn.t()) :: String.t() | nil

Resolve the prefix from an explicit config or connection.

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.

The configured :principal_kinds list or callback is resolved once for each call. An explicit extra value still wins over the derived values. Any other Attesto.Config.new/1 option may be supplied as extra; those options are merged over the values derived here.

token_endpoint_auth_methods_supported(config)

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

The client-authentication methods accepted at the token endpoint.

This is the single catalog used by endpoint enforcement, discovery, and dynamic registration. When no explicit catalog is configured, wallet attestation is added only when trusted Wallet Provider keys are available. An explicitly configured catalog is never widened. The wallet-attestation method is omitted whenever its trusted verification keys are absent, including from an explicit list.

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.

trusted_wallet_provider_jwks(config)

@spec trusted_wallet_provider_jwks(t()) :: map() | [map()] | nil

Trusted Wallet Provider keys for attestation-based client authentication.

Returns an RFC 7517 JWK Set, a single public JWK map, a list of public JWK maps, or nil when attest_jwt_client_auth is disabled.

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. This is the URL selected by userinfo_endpoint: :derived; an explicit URL or nil remains authoritative in Provider Metadata.

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.

vc_keystore(config)

@spec vc_keystore(t()) :: module()

The keystore used to sign issued Verifiable Credentials; defaults to :keystore. Configure a separate EC/ES256 keystore here to issue ES256-signed credentials (e.g. for HAIP) while ID tokens keep their own signing key.

vc_signing_pem(config)

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

Returns the PEM used to sign issued Verifiable Credentials.

vc_signing_x5c(config)

@spec vc_signing_x5c(t()) :: [String.t()] | nil

The VC signing key's X.509 certificate chain, or nil.

A list of base64 DER certificate strings stamped as the issued credential's JOSE x5c header (HAIP), sourced from the VC keystore's optional x5c/0 callback. nil when the keystore does not provide one.

verifier_client_id(config)

@spec verifier_client_id(t()) :: String.t() | nil

The verifier client identifier used as the OID4VP presentation audience.

verifier_client_id_scheme(config)

@spec verifier_client_id_scheme(t()) :: String.t() | nil

The OID4VP verifier client-id scheme, or nil for the default behavior.

verifier_dns(config)

@spec verifier_dns(t()) :: String.t() | nil

The dNSName advertised by an x509_san_dns verifier.

verifier_encryption_keystore(config)

@spec verifier_encryption_keystore(t()) :: module() | nil

The dedicated EC P-256 keystore for encrypted OID4VP responses, or nil.

verifier_x5c(config)

@spec verifier_x5c(t()) :: [binary()] | nil

The verifier certificate chain as DER binaries, leaf first.

verify_client_secret_fun(config)

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

Resolve the verify_client_secret callback. See resolve_callback/2.

with_request_config(config, fun)

@spec with_request_config(t(), (-> result)) :: result when result: var

Run a conn-free callback with config as the request-local configuration.

The previous process-local value is restored even if fun raises. This is useful to hosts that invoke a store outside the normal Plug request path.