# `X402.Extensions.AuthHints`
[🔗](https://github.com/cardotrejos/x402/blob/v0.9.0/lib/x402/extensions/auth_hints.ex#L1)

The x402 `auth-hints` extension: per-requirement authentication hints.

Implements the
[auth-hints extension](https://github.com/x402-foundation/x402/blob/main/specs/extensions/extension-auth-hints.md).
A resource server whose `accepts[]` entries require authentication maps
them to the authentication methods that satisfy them under
`PaymentRequired.extensions["auth-hints"]`, so a client can register
and obtain credentials *before* submitting a payment instead of
discovering the requirement through a `401`:

    %{
      "auth-hints" => %{
        "info" => %{
          "authRequirements" => [
            %{"acceptIndexes" => [1], "methods" => [%{"type" => "oauth2", ...}]}
          ]
        },
        "schema" => %{...}
      }
    }

Two method types are defined: `oauth2` (`oauth2/1`), carrying the token
type and the authorization server's endpoints, and `sign-in-with-x`
(`sign_in_with_x/0`), a pointer to the `sign-in-with-x` extension on
the same response (`X402.Extensions.SIWX`). Unknown types are preserved
on decode so clients can skip what they do not support.

## Server

Advertise hints from a gate route with `X402.Extensions.AuthHints.Adapter`
or build the value yourself with `extension/1`:

    X402.Extensions.AuthHints.extension([
      [accept_indexes: [1], methods: [X402.Extensions.AuthHints.oauth2(
        token_type: "DPoP",
        authorization_server: "https://as.example.com",
        token_endpoint: "https://as.example.com/token",
        registration_endpoint: "https://as.example.com/register"
      )]]
    ])

The hints are discovery metadata only: validating the credentials the
client then presents (`Authorization`, `DPoP`, `SIGN-IN-WITH-X`) stays
with the application, and the facilitator is not involved.

## Client

`methods_for/2` returns the methods a chosen `accepts[]` entry requires
— an empty list when it needs none — so a `X402.Client.Hooks`
`before_payment/2` implementation can complete the flow first:

    def before_payment(%{payment_required: payment_required, requirements: chosen} = context, _meta) do
      case X402.Extensions.AuthHints.methods_for(payment_required, chosen) do
        [] -> {:ok, context}
        methods -> authenticate(methods, context)
      end
    end

Indexes that fall outside `accepts[]` are silently ignored, as the
specification requires of clients.

# `error`

```elixir
@type error() ::
  :invalid_auth_hints
  | :invalid_accept_indexes
  | :invalid_method
  | {:missing_method_field, String.t()}
  | :invalid_token_type
```

Errors returned by `decode/1` and `validate_method/1`.

# `method`

```elixir
@type method() :: %{required(String.t()) =&gt; term()}
```

A method map as it appears on the wire.

# `requirement`

```elixir
@type requirement() :: %{accept_indexes: [non_neg_integer()], methods: [method()]}
```

A decoded requirement.

# `decode`
*since 0.9.0* 

```elixir
@spec decode(term()) :: {:ok, [requirement()]} | {:error, error()}
```

Decodes the requirements from a `PaymentRequired` map (or its
`extensions` map).

Returns `{:ok, []}` when the extension is absent. Indexes outside the
response's `accepts[]` are dropped (when the map given is the full
`PaymentRequired`); a requirement left with no index is dropped too.
Methods are checked with `validate_method/1`.

## Examples

    iex> extension = X402.Extensions.AuthHints.extension([
    ...>   [accept_indexes: [1, 7], methods: [X402.Extensions.AuthHints.sign_in_with_x()]]
    ...> ])
    iex> payment_required = %{"accepts" => [%{}, %{}], "extensions" => %{"auth-hints" => extension}}
    iex> X402.Extensions.AuthHints.decode(payment_required)
    {:ok, [%{accept_indexes: [1], methods: [%{"type" => "sign-in-with-x"}]}]}

    iex> X402.Extensions.AuthHints.decode(%{"accepts" => [], "extensions" => %{}})
    {:ok, []}

    iex> X402.Extensions.AuthHints.decode(%{"extensions" => %{"auth-hints" => %{"info" => %{"authRequirements" => "x"}}}})
    {:error, :invalid_auth_hints}

    iex> hints = %{"info" => %{"authRequirements" => [%{"acceptIndexes" => [0], "methods" => [%{"type" => "oauth2"}]}]}}
    iex> X402.Extensions.AuthHints.decode(%{"accepts" => [%{}], "extensions" => %{"auth-hints" => hints}})
    {:error, {:missing_method_field, "tokenType"}}

# `extension`
*since 0.9.0* 

```elixir
@spec extension([keyword()]) :: map()
```

Builds the server-side advertisement for `PaymentRequired.extensions`.

Each requirement is a keyword list with `:accept_indexes` and
`:methods`. Raises `NimbleOptions.ValidationError` for an invalid
declaration, including an empty requirement list.

## Requirement options

* `:accept_indexes` - Required. Indexes into `accepts[]` that require authentication (at least one).

* `:methods` - Required. Methods that satisfy the requirement (at least one), as built by `oauth2/1` or `sign_in_with_x/0`.

## Examples

    iex> extension = X402.Extensions.AuthHints.extension([
    ...>   [accept_indexes: [1], methods: [X402.Extensions.AuthHints.sign_in_with_x()]]
    ...> ])
    iex> extension["info"]
    %{"authRequirements" => [%{"acceptIndexes" => [1], "methods" => [%{"type" => "sign-in-with-x"}]}]}
    iex> extension["schema"] == X402.Extensions.AuthHints.schema()
    true

# `extension_key`
*since 0.9.0* 

```elixir
@spec extension_key() :: String.t()
```

Returns the extension key on the wire.

## Examples

    iex> X402.Extensions.AuthHints.extension_key()
    "auth-hints"

# `methods_for`
*since 0.9.0* 

```elixir
@spec methods_for(map(), non_neg_integer() | map()) :: [method()]
```

Returns the methods that satisfy authentication for one `accepts[]`
entry, given by index or by the requirements map itself.

Methods from every requirement naming the entry are concatenated in
order. Returns `[]` when no authentication is needed, when the entry is
not found, or when the extension is malformed — the client then
proceeds without credentials and the server answers as it would for
any unauthenticated request.

## Examples

    iex> oauth2 = X402.Extensions.AuthHints.oauth2(token_type: "DPoP",
    ...>   authorization_server: "https://as.example.com", token_endpoint: "https://as.example.com/token")
    iex> extension = X402.Extensions.AuthHints.extension([[accept_indexes: [1], methods: [oauth2]]])
    iex> exact = %{"scheme" => "exact", "network" => "eip155:8453"}
    iex> deferred = %{"scheme" => "deferred", "network" => "eip155:8453"}
    iex> payment_required = %{"accepts" => [exact, deferred], "extensions" => %{"auth-hints" => extension}}
    iex> X402.Extensions.AuthHints.methods_for(payment_required, deferred) == [oauth2]
    true
    iex> X402.Extensions.AuthHints.methods_for(payment_required, 1) == [oauth2]
    true
    iex> X402.Extensions.AuthHints.methods_for(payment_required, exact)
    []
    iex> X402.Extensions.AuthHints.methods_for(payment_required, 5)
    []

# `oauth2`
*since 0.9.0* 

```elixir
@spec oauth2(keyword()) :: method()
```

Builds an `oauth2` method.

Raises `NimbleOptions.ValidationError` for invalid options: a malformed
hint is a configuration error.

## Options

* `:token_type` - Required. How the access token is presented: `"Bearer"` or `"DPoP"`.

* `:authorization_server` - Required. Base URL of the authorization server.

* `:token_endpoint` - Required. Token endpoint URL.

* `:registration_endpoint` - RFC 7591 dynamic client registration endpoint, when registration is available.

## Examples

    iex> X402.Extensions.AuthHints.oauth2(
    ...>   token_type: "DPoP",
    ...>   authorization_server: "https://as.example.com",
    ...>   token_endpoint: "https://as.example.com/token",
    ...>   registration_endpoint: "https://as.example.com/register"
    ...> )
    %{
      "type" => "oauth2",
      "tokenType" => "DPoP",
      "authorizationServer" => "https://as.example.com",
      "tokenEndpoint" => "https://as.example.com/token",
      "registrationEndpoint" => "https://as.example.com/register"
    }

    iex> X402.Extensions.AuthHints.oauth2(
    ...>   token_type: "Bearer",
    ...>   authorization_server: "https://as.example.com",
    ...>   token_endpoint: "https://as.example.com/token"
    ...> )
    %{
      "type" => "oauth2",
      "tokenType" => "Bearer",
      "authorizationServer" => "https://as.example.com",
      "tokenEndpoint" => "https://as.example.com/token"
    }

# `requires_auth?`
*since 0.9.0* 

```elixir
@spec requires_auth?(map(), non_neg_integer() | map()) :: boolean()
```

Returns whether an `accepts[]` entry (by index or map) requires
authentication.

## Examples

    iex> extension = X402.Extensions.AuthHints.extension([
    ...>   [accept_indexes: [0], methods: [X402.Extensions.AuthHints.sign_in_with_x()]]
    ...> ])
    iex> payment_required = %{"accepts" => [%{"scheme" => "exact"}, %{"scheme" => "upto"}], "extensions" => %{"auth-hints" => extension}}
    iex> X402.Extensions.AuthHints.requires_auth?(payment_required, 0)
    true
    iex> X402.Extensions.AuthHints.requires_auth?(payment_required, %{"scheme" => "upto"})
    false

# `schema`
*since 0.9.0* 

```elixir
@spec schema() :: map()
```

Returns the JSON schema the server advertises for the extension.

## Examples

    iex> schema = X402.Extensions.AuthHints.schema()
    iex> schema["required"]
    ["authRequirements"]
    iex> get_in(schema, ["properties", "authRequirements", "items", "required"])
    ["acceptIndexes", "methods"]

# `sign_in_with_x`
*since 0.9.0* 

```elixir
@spec sign_in_with_x() :: method()
```

Builds a `sign-in-with-x` method: a pointer to the `sign-in-with-x`
extension advertised on the same response.

## Examples

    iex> X402.Extensions.AuthHints.sign_in_with_x()
    %{"type" => "sign-in-with-x"}

# `validate_method`
*since 0.9.0* 

```elixir
@spec validate_method(term()) :: {:ok, method()} | {:error, error()}
```

Validates a method map as it appears on the wire.

`oauth2` must carry a `tokenType` of `Bearer` or `DPoP` and string
`authorizationServer` and `tokenEndpoint` values; `sign-in-with-x`
needs only its type. Other types are accepted as long as `type` is a
string, so clients can ignore methods they do not implement.

## Examples

    iex> X402.Extensions.AuthHints.validate_method(%{"type" => "sign-in-with-x"})
    {:ok, %{"type" => "sign-in-with-x"}}

    iex> X402.Extensions.AuthHints.validate_method(%{"type" => "oauth2", "tokenType" => "Bearer",
    ...>   "authorizationServer" => "https://as.example.com"})
    {:error, {:missing_method_field, "tokenEndpoint"}}

    iex> X402.Extensions.AuthHints.validate_method(%{"type" => "oauth2", "tokenType" => "MAC",
    ...>   "authorizationServer" => "https://as.example.com", "tokenEndpoint" => "https://as.example.com/token"})
    {:error, :invalid_token_type}

    iex> X402.Extensions.AuthHints.validate_method(%{"type" => "future"})
    {:ok, %{"type" => "future"}}

    iex> X402.Extensions.AuthHints.validate_method(%{"tokenType" => "Bearer"})
    {:error, :invalid_method}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
