# `ExMCP.Authorization.JWT`
[🔗](https://github.com/azmaveth/ex_mcp/blob/v1.0.0/lib/ex_mcp/authorization/jwt.ex#L1)

General-purpose JWT module wrapping JOSE for MCP authorization.

Provides key loading, JWT signing, verification, and claims validation
used by OAuth client assertions, ID-JAG tokens, and JWT bearer grants.

# `fetch_jwks`

```elixir
@spec fetch_jwks(
  String.t(),
  keyword()
) :: {:ok, [JOSE.JWK.t()]} | {:error, term()}
```

Fetches a JWKS (JSON Web Key Set) from a URL.

Uses the same HTTPS-only, public-address, pinned and bounded fetch policy as
authorization-server and client metadata discovery. Custom HTTP clients must
implement `get(uri, approved_address, opts)`.

# `generate_ec_key`

```elixir
@spec generate_ec_key(keyword()) :: JOSE.JWK.t()
```

Generates an EC key pair for development/testing.

## Options
  - `:curve` - EC curve name (default: "P-256")

# `generate_jti`

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

Generates a unique JWT ID (jti).

# `generate_rsa_key`

```elixir
@spec generate_rsa_key(keyword()) :: JOSE.JWK.t()
```

Generates an RSA key pair for development/testing.

## Options
  - `:size` - Key size in bits (default: 2048)

# `load_key`

```elixir
@spec load_key(map() | String.t() | {:pem_file, String.t()}) ::
  {:ok, JOSE.JWK.t()} | {:error, term()}
```

Loads a JWK from a PEM string, JWK map, or file path.

## Examples

    {:ok, jwk} = JWT.load_key(%{"kty" => "RSA", ...})
    {:ok, jwk} = JWT.load_key("-----BEGIN RSA PRIVATE KEY-----\n...")
    {:ok, jwk} = JWT.load_key({:pem_file, "/path/to/key.pem"})

# `peek_header`

```elixir
@spec peek_header(String.t()) :: {:ok, map()} | {:error, term()}
```

Reads the unverified header from a JWS token (for typ checking).

# `sign`

```elixir
@spec sign(map(), JOSE.JWK.t(), keyword()) :: {:ok, String.t()} | {:error, term()}
```

Signs a claims map into a compact JWS string.

## Options
  - `:alg` - Signing algorithm (default: "RS256")
  - `:kid` - Key ID to include in header
  - `:typ` - Token type header (default: "JWT")

# `to_map`

```elixir
@spec to_map(JOSE.JWK.t()) :: map()
```

Converts a JWK to a map representation (for JWKS publishing).

# `to_public_key`

```elixir
@spec to_public_key(JOSE.JWK.t()) :: JOSE.JWK.t()
```

Extracts the public key from a JWK.

# `validate_claims`

```elixir
@spec validate_claims(
  map(),
  keyword()
) :: {:ok, map()} | {:error, term()}
```

Validates standard JWT claims against expected values.

## Time-based claims

`exp` is **required by default** and must be a number: a token without an
expiry, or with a non-numeric expiry, is rejected. `nbf` and `iat` are
optional, but when present they must be numeric — a non-numeric value is
rejected rather than ignored.

All time comparisons allow `:leeway` seconds of clock skew (default
`30`).

## Options
  - `:leeway` - Clock skew allowance in seconds (default: `30`)
  - `:require_exp` - Require an `exp` claim (default: `true`)
  - `:iss` - Expected issuer
  - `:aud` - Expected audience (string or list)
  - `:sub` - Expected subject
  - `:max_age` - Maximum token age in seconds
  - `:required` - List of required claim keys (as strings)

## Errors

  - `{:error, :missing_exp}` - no `exp` claim and `require_exp` is true
  - `{:error, {:invalid_claim_type, "exp" | "nbf" | "iat"}}` - claim present but not a number
  - `{:error, :token_expired}` / `{:error, :token_not_yet_valid}` / `{:error, :invalid_iat}`

# `verify`

```elixir
@spec verify(String.t(), JOSE.JWK.t() | [JOSE.JWK.t()]) ::
  {:ok, map()} | {:error, term()}
```

Verifies the *signature* of a JWS string and returns the decoded claims.

Accepts a single JWK or a list of JWKs (JWKS).

> #### Signature verification only {: .warning}
>
> This function performs **no claims validation**: an expired token, a
> token that is not yet valid, or a token issued for another audience all
> verify successfully as long as the signature is good. Use
> `verify_and_validate/3` (or `validate_claims/2` on the result) for
> anything that makes an authorization decision.

# `verify_and_validate`

```elixir
@spec verify_and_validate(String.t(), JOSE.JWK.t() | [JOSE.JWK.t()], keyword()) ::
  {:ok, map()} | {:error, term()}
```

Verifies a JWS string and validates claims against expected values.

Takes the same options as `validate_claims/2`. Note that `exp` is required
by default; pass `require_exp: false` for the rare token profile that
legitimately omits it.

---

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