# `Attesto.Config`
[🔗](https://github.com/XukuLLC/attesto/blob/v0.13.0/lib/attesto/config.ex#L1)

Immutable configuration a token operation runs against.

A `Config` binds together everything `Attesto.Token` needs that is
policy rather than protocol: who the issuer and audience are, where keys
come from, what principal kinds exist, and the default token lifetime.
Build one once (typically memoised by the host application) and pass it
to `Attesto.Token.mint/3` and `Attesto.Token.verify/3`.

## Fields

  * `:issuer` - the `iss` claim value minted into every token and
    required to match on verify. A non-empty string (an `https://` URL
    in any real deployment).
  * `:audience` - the `aud` claim value. A non-empty string.
  * `:keystore` - a module implementing `Attesto.Keystore`.
  * `:principal_kinds` - the non-empty list of `Attesto.PrincipalKind`
    structs this issuer serves. Their `claim_value`s must be distinct,
    and their `sub_prefix`es must be distinct, so the kind a token
    claims and the subject it carries map unambiguously.
  * `:principal_kind_claim` - the JWT claim name that carries the
    principal kind's `claim_value`. Defaults to `"principal_kind"`.
    A host may set its own (e.g. a namespaced private claim) without
    changing any other behaviour.
  * `:default_lifetime_seconds` - access-token lifetime when a caller
    does not request a shorter one. Defaults to `900` (15 minutes).
  * `:token_endpoint_path` - the request path the token endpoint is
    mounted at, used to derive `token_endpoint_url/1` (the URL a DPoP
    proof's `htu` must sign, and the URL OAuth metadata would publish).
    Defaults to `"/oauth/token"`.

## Reserved claims

The claim names Attesto assembles itself - `iss`, `aud`, `exp`, `iat`,
`jti`, `sub`, `scope`, `typ`, `cnf`, and the configured
`principal_kind_claim` - are reserved. `Attesto.Token.mint/3` refuses a
principal whose extra claims would collide with one of them, so a caller
can never shadow a protocol claim.

# `t`

```elixir
@type t() :: %Attesto.Config{
  access_token_header_typ: String.t() | nil,
  audience: String.t(),
  default_lifetime_seconds: pos_integer(),
  issuer: String.t(),
  keystore: module(),
  principal_kind_claim: String.t(),
  principal_kinds: [Attesto.PrincipalKind.t(), ...],
  token_endpoint_path: String.t()
}
```

# `new`

```elixir
@spec new(keyword()) :: t()
```

Build and validate a `Config`.

    Attesto.Config.new(
      issuer: "https://api.example/",
      audience: "https://api.example/",
      keystore: MyApp.Keystore,
      principal_kinds: [
        Attesto.PrincipalKind.new("client", "oc_",
          required_claims: [{"client_id", :non_empty_string}]),
        Attesto.PrincipalKind.new("user", "usr_",
          required_claims: [
            {"act", :non_empty_string},
            {"sid", :non_empty_string},
            {"token_version", :non_neg_integer}
          ])
      ]
    )

Raises `ArgumentError` on a malformed configuration (blank issuer or
audience, a keystore that is not a module, an empty or non-list
principal-kind set, duplicate `claim_value`s or `sub_prefix`es, or a
`principal_kind_claim` that collides with a reserved claim). This is
evaluated once at boot, so it fails loudly rather than at the first
token operation.

# `principal_kind`

```elixir
@spec principal_kind(t(), term()) :: Attesto.PrincipalKind.t() | nil
```

Return the `Attesto.PrincipalKind` whose `claim_value` equals
`claim_value`, or `nil` if no configured kind matches.

# `token_endpoint_url`

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

The canonical external URL of the token endpoint: the configured issuer
merged with `token_endpoint_path`. This is the URL a client's DPoP proof
must sign in its `htu` claim (RFC 9449 §4.3) and the URL OAuth
Authorization Server Metadata (RFC 8414) would publish. Derived from
`issuer` rather than a live request so it is stable behind any TLS
terminator or reverse proxy.

---

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