Exosphere.ATProto.OAuth.ClientMetadata (Exosphere v0.4.0)

Copy Markdown View Source

OAuth 2.0 Client Metadata Documents (the client_id document).

In ATProto OAuth the client_id is a URL: the authorization server fetches the client metadata document from it. Web ("confidential") clients serve the document themselves and embed a public key (jwks), which authorizes them for private_key_jwt client authentication. Native/loopback clients instead use the special http://localhost client identifier built by localhost_client_id/2 and skip the document entirely.

Serve to_document/1 as JSON at your client_id URL, and keep the matching private key secret — Exosphere.ATProto.OAuth.Client pairs the two at runtime.

Examples

iex> metadata = Exosphere.ATProto.OAuth.ClientMetadata.new!(
...>   client_id: "https://app.example.com/oauth-client-metadata.json",
...>   client_name: "My App",
...>   redirect_uris: ["https://app.example.com/oauth/callback"],
...>   scope: ["atproto", "transition:generic"],
...>   jwk: public_jwk
...> )
iex> document = Exosphere.ATProto.OAuth.ClientMetadata.to_document(metadata)

Summary

Functions

Whether this is a confidential client (uses private_key_jwt client authentication with a hosted key).

The special loopback client identifier for local development: http://localhost with redirect_uri and scope query parameters, as allowed by the ATProto OAuth profile.

Build and validate client metadata.

Like new/1, but raises on invalid input.

Render the Client Metadata Document (to_json-ready map) served at client_id.

Types

t()

@type t() :: %Exosphere.ATProto.OAuth.ClientMetadata{
  application_type: :web | :native,
  client_id: String.t(),
  client_name: String.t() | nil,
  client_uri: String.t() | nil,
  dpop_bound_access_tokens: boolean(),
  grant_types: [:authorization_code | :refresh_token],
  jwk: Exosphere.ATProto.OAuth.JWK.t() | nil,
  logo_uri: String.t() | nil,
  policy_uri: String.t() | nil,
  redirect_uris: [String.t()],
  response_types: [:code],
  scope: [String.t()],
  token_endpoint_auth_method: :private_key_jwt | :none,
  token_endpoint_auth_signing_alg: String.t(),
  tos_uri: String.t() | nil
}

Functions

confidential?(arg1)

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

Whether this is a confidential client (uses private_key_jwt client authentication with a hosted key).

localhost_client_id(redirect_uris, scope \\ ["atproto", "transition:generic"])

@spec localhost_client_id([String.t()], [String.t()]) ::
  {:ok, String.t()} | {:error, term()}

The special loopback client identifier for local development: http://localhost with redirect_uri and scope query parameters, as allowed by the ATProto OAuth profile.

The authorization server assembles a virtual metadata document from these parameters; no hosted document or client key is needed.

new(opts)

@spec new(keyword()) :: {:ok, t()} | {:error, term()}

Build and validate client metadata.

Options

  • :client_id (required) - URL of the metadata document itself (https, or http://localhost... for local development)
  • :redirect_uris (required) - at least one https (or loopback) redirect URI
  • :scope - scope strings; must include "atproto" (default ["atproto", "transition:generic"])
  • :jwk - public JWK map for confidential clients (private_key_jwt); omit for public clients (which then use token_endpoint_auth_method: :none)
  • :client_name, :client_uri, :logo_uri, :tos_uri, :policy_uri - display metadata (all URIs must be https)
  • :application_type - :web (default) or :native
  • :token_endpoint_auth_method - inferred from :jwk presence when unset

new!(opts)

@spec new!(keyword()) :: t()

Like new/1, but raises on invalid input.

to_document(metadata)

@spec to_document(t()) :: map()

Render the Client Metadata Document (to_json-ready map) served at client_id.