AttestoClient.Discovery (AttestoClient v2.4.1)

Copy Markdown View Source

Fetch and read OAuth 2.0 / OpenID Connect authorization-server metadata (RFC 8414 / OpenID Connect Discovery 1.0).

A thin lookup: it fetches the discovery document (and, separately, the JWKS) and returns the parsed JSON. It runs no flow and holds no state - the host reads the endpoint URLs and capabilities it needs (and, for verifying JARM responses, passes the fetched JWKS to AttestoClient.JARM.verify/3).

URL construction

By default the OpenID Connect Discovery document is fetched by appending /.well-known/openid-configuration to the issuer (OpenID Connect Discovery 1.0 §4). Pass well_known: :oauth_authorization_server for the RFC 8414 document, which is constructed differently: the well-known segment is inserted before the issuer's path component (RFC 8414 §3.1), so it is correct for path-based (multi-tenant) issuers.

Issuer / transport validation

  • The issuer MUST be an https URL with no query or fragment (RFC 8414 §2).
  • The document's own issuer member MUST be identical to the issuer it was fetched for (RFC 8414 §3.3 / OpenID Connect Discovery 1.0 §4.3), defending against a metadata mix-up. The comparison is exact - no trailing-slash normalisation - so a path-based issuer that ends in / (e.g. a multi-tenant issuer) must be supplied exactly as the server publishes it. A trailing slash is removed only when constructing the well-known request URL, as both specs require.
  • A JWKS is fetched only over https, since it is the trust root for verifying the authorization server's signatures.

HTTP

Requests go through Req. Pass :req_options for safe transport configuration - notably plug: for Req.Test in tests. The library owns the destination, method, authority, redirects, request body, cache policy, and TLS peer identity; conflicting options are removed or fail closed. Discovery and JWKS requests never carry caller credentials. Their overall deadline includes DNS screening and request preparation, not only the socket receive phase.

Summary

Functions

Fetch the authorization server's metadata for issuer, returning {:ok, metadata} (a string-keyed map) or {:error, reason}.

Fetch a JWKS document from jwks_uri (typically the metadata's jwks_uri), returning {:ok, jwks} - a map with a "keys" list - or {:error, reason}. The URI must be https.

Preflight an authorization-server endpoint against the URL and address policy. The endpoint must use HTTPS, must not contain userinfo or a fragment, and must not resolve to a private, loopback, or link-local address.

Types

error()

@type error() ::
  :invalid_issuer
  | :invalid_well_known
  | :invalid_jwks_uri
  | :issuer_mismatch
  | :invalid_metadata
  | :response_too_large
  | :blocked_host
  | :unsafe_transport_options
  | :invalid_timeout
  | :invalid_resolver
  | :timeout
  | {:http_status, pos_integer()}
  | {:transport, term()}

opt()

@type opt() ::
  {:well_known, well_known()}
  | {:req_options, keyword()}
  | {:max_response_bytes, pos_integer()}
  | {:timeout, pos_integer()}
  | {:resolver,
     (charlist(), :inet | :inet6 ->
        {:ok, [:inet.ip_address()]} | {:error, term()})}

well_known()

@type well_known() :: :openid_configuration | :oauth_authorization_server

Functions

fetch(issuer, opts \\ [])

@spec fetch(String.t(), [opt()]) :: {:ok, map()} | {:error, error()}

Fetch the authorization server's metadata for issuer, returning {:ok, metadata} (a string-keyed map) or {:error, reason}.

issuer must be an https URL with no query or fragment. Options: :well_known (:openid_configuration (default) or :oauth_authorization_server) and :req_options (safely merged into the Req transport configuration as described above). :timeout is the overall discovery deadline in milliseconds, including DNS resolution and request preparation (default: 10 seconds).

fetch_jwks(jwks_uri, opts \\ [])

@spec fetch_jwks(String.t(), [opt()]) :: {:ok, map()} | {:error, error()}

Fetch a JWKS document from jwks_uri (typically the metadata's jwks_uri), returning {:ok, jwks} - a map with a "keys" list - or {:error, reason}. The URI must be https.

validate_endpoint(endpoint, opts \\ [])

@spec validate_endpoint(
  term(),
  keyword()
) :: :ok | {:error, :invalid_endpoint | :blocked_host | :invalid_resolver}

Preflight an authorization-server endpoint against the URL and address policy. The endpoint must use HTTPS, must not contain userinfo or a fragment, and must not resolve to a private, loopback, or link-local address.

This check does not bind a later, separate socket connection to the address it inspected, so it is not by itself a complete SSRF defense. Prefer this library's request APIs, which carry the screened address through to the transport while retaining the original hostname for TLS and HTTP authority.

Applications normally use this indirectly through the authorization-code, refresh, and revocation APIs.