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

Copy Markdown View Source

DPoP (RFC 9449) proof JWTs for the ATProto OAuth profile.

Every request an ATProto OAuth client makes — the PAR request, the token requests, and each resource (XRPC) request — carries a fresh proof JWT in a DPoP header, signed by the session's DPoP key, binding the request's method and URI (and, for resource requests, the access token via ath).

Servers may require a server-issued nonce (400 with use_dpop_nonce and a DPoP-Nonce response header); Exosphere.ATProto.OAuth.Request handles the retry loop, backed by Exosphere.ATProto.OAuth.DPoP.NonceStore.

Examples

{:ok, key} = Exosphere.ATProto.OAuth.DPoP.generate_key()
proof = Exosphere.ATProto.OAuth.DPoP.proof(key, "POST", "https://pds.example.com/xrpc/com.atproto.repo.describeRepo",
  ath: "eyJ...")

Summary

Functions

Generate a fresh P-256 DPoP keypair as a private JWK map.

Whether an error response is a DPoP nonce challenge that should be retried (400 with error use_dpop_nonce, or 401 carrying a DPoP-Nonce header) — and if so, the nonce to use.

Extract the DPoP-Nonce header from a response header list.

Normalize a URL into an htu per RFC 9449 §4.3: scheme and host lowercased, default ports dropped (non-default ports kept), path retained (empty becomes /), query and fragment stripped.

The origin (scheme://host[:port], default ports dropped) of a URL.

Mint a DPoP proof JWT for a request.

Types

private_key()

@type private_key() :: Exosphere.ATProto.OAuth.JWK.t()

Functions

generate_key()

@spec generate_key() :: {:ok, private_key()}

Generate a fresh P-256 DPoP keypair as a private JWK map.

ATProto clients generate a new DPoP key per user/device/session, starting with the PAR request.

nonce_challenge?(arg1)

@spec nonce_challenge?(%{
  status: pos_integer(),
  headers: [{String.t(), String.t()}],
  body: term()
}) ::
  String.t() | nil

Whether an error response is a DPoP nonce challenge that should be retried (400 with error use_dpop_nonce, or 401 carrying a DPoP-Nonce header) — and if so, the nonce to use.

nonce_header(headers)

@spec nonce_header([{String.t(), String.t()}]) :: String.t() | nil

Extract the DPoP-Nonce header from a response header list.

normalize_htu(url)

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

Normalize a URL into an htu per RFC 9449 §4.3: scheme and host lowercased, default ports dropped (non-default ports kept), path retained (empty becomes /), query and fragment stripped.

origin(url)

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

The origin (scheme://host[:port], default ports dropped) of a URL.

Used as the key for per-authorization-server nonce tracking.

proof(private_key, htm, htu, opts \\ [])

@spec proof(private_key(), String.t(), String.t(), keyword()) ::
  {:ok, binary()} | {:error, term()}

Mint a DPoP proof JWT for a request.

Options

  • :nonce - server-issued nonce (required when the server has issued one)
  • :ath - access token; when present, its SHA-256 hash is bound into the proof as ath (resource-server requests)
  • :iat - issuance time in seconds (defaults to now); for tests