Snodo.Client.Challenge (snodo v0.4.1)

Copy Markdown View Source

One authentication challenge from a WWW-Authenticate response header.

A 401 or 403 from an MCP server over HTTP carries the challenge that tells a client how to authorize (RFC 9110 section 11.6.1, RFC 6750 section 3, and the MCP authorization specification):

WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp", scope="mcp:read"
WWW-Authenticate: Bearer error="insufficient_scope", scope="mcp:write"

parse/1 reads every challenge in one header value. select/1 picks the challenge a token provider acts on from a response's headers. The struct keeps every parameter in params, keyed by lowercase name, and lifts the ones the MCP flow reads:

  • resource_metadata - the URL of the protected resource metadata document (RFC 9728).
  • scope - the scopes the server asks for, split on spaces.
  • error and error_description - the RFC 6750 error, for example "invalid_token" or "insufficient_scope".

A header longer than 8 KiB, or one that does not follow the grammar, parses as no challenges. A parameter that appears twice keeps its first value. A challenge that carries a token68 credential instead of parameters, such as Negotiate abc==, has empty params, and the challenges after it are still read.

Summary

Functions

Parses one WWW-Authenticate header value into its challenges, in order.

Picks the challenge to act on from a response's {name, value} headers.

Types

t()

@type t() :: %Snodo.Client.Challenge{
  error: String.t() | nil,
  error_description: String.t() | nil,
  params: %{optional(String.t()) => String.t()},
  resource_metadata: String.t() | nil,
  scheme: String.t(),
  scope: [String.t()]
}

Functions

parse(header)

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

Parses one WWW-Authenticate header value into its challenges, in order.

Parameter values may be tokens or quoted strings; a quoted string is unescaped. Schemes and parameter names are lowercased. An empty list means the header carries no usable challenge.

select(headers)

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

Picks the challenge to act on from a response's {name, value} headers.

Every www-authenticate header (compared without case) is parsed. The first Bearer challenge wins; without one, the first challenge of any scheme is returned, so a provider can report that it does not support the scheme. nil when the response carries no challenge.