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

Identity Assertion JWT Authorization Grant (ID-JAG) verification - the resource
Authorization Server's half of the Identity Assertion Authorization Grant
(`draft-ietf-oauth-identity-assertion-authz-grant-04`), the grant behind MCP
Enterprise-Managed Authorization (EMA).

In EMA the client first performs an RFC 8693 token exchange *at the enterprise
IdP*, trading the user's ID token / SAML assertion for an **ID-JAG**: a
short-lived JWT, signed by the IdP, asserting one user for one resource
application. The client then presents that ID-JAG to *this* server's token
endpoint as an RFC 7523 §4 JWT-bearer authorization grant
(`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer`, the assertion in the
`assertion` parameter) and receives a normal access token. This module verifies
that assertion.

It is deliberately **conn-free and side-effect-free**: it verifies the
compact JWT's signature against a caller-supplied trusted JWKS and validates
the draft's claim rules, returning the claims or a typed error. The caller
(the `AttestoPhoenix` token layer) owns the stateful concerns: resolving which
trusted issuer's JWKS to use (and fetching/caching it), `jti` replay
protection, subject resolution, and mapping every error here to the RFC 6749
§5.2 `invalid_grant` the token endpoint must return.

This is NOT `private_key_jwt` client authentication (RFC 7523 §3, which asserts
the *client's* identity) nor the RFC 8693 token-exchange grant (which runs at
the IdP, not here). It shares JWT-validation shape with
`Attesto.RequestObject` but enforces ID-JAG's distinct claim rules - notably
`iss` is the IdP (NOT equal to `client_id`), `aud` is this server's issuer,
and the JOSE `typ` is pinned to `oauth-id-jag+jwt`.

## Validated per the draft

  * JOSE header `typ` MUST be `oauth-id-jag+jwt` (a media type, compared
    case-insensitively per RFC 7515 §4.1.9 / RFC 2045 §5.1).
  * signature verifies against the trusted issuer JWKS, selecting the key by
    `kid` and the accepted algorithms.
  * `iss` matches the caller-supplied trusted issuer.
  * `aud` is exactly this server's issuer identifier - a single string, or an
    array of exactly one element equal to it (draft §6.1).
  * `client_id` matches the authenticated client making the token request.
  * the REQUIRED claims `iss`, `sub`, `aud`, `client_id`, `jti`, `exp`, `iat`
    are present and well-typed.
  * `exp` is in the future; `iat`/`nbf` are not in the future (60s skew); the
    lifetime `exp - iat` does not exceed `:max_lifetime_seconds` when set.

# `claims`

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

The validated, string-keyed ID-JAG claim set.

# `verify_error`

```elixir
@type verify_error() ::
  :malformed
  | :unsupported_critical_header
  | :unsupported_alg
  | :invalid_typ
  | :invalid_signature
  | :invalid_issuer
  | :invalid_audience
  | :missing_claim
  | :client_mismatch
  | :expired
  | :not_yet_valid
```

# `verify_opts`

```elixir
@type verify_opts() :: [
  now: DateTime.t() | non_neg_integer(),
  issuer: String.t(),
  audience: String.t(),
  client_id: String.t(),
  accepted_algs: [Attesto.SigningAlg.alg()],
  max_lifetime_seconds: pos_integer() | nil
]
```

# `peek_issuer`

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

Read the unverified `iss` claim from an assertion so the caller can select the
trusted issuer (and its JWKS) before verifying the signature.

This decodes the JWT payload WITHOUT verifying it - the returned issuer is
untrusted until `verify/3` confirms the signature and re-checks `iss` against
the caller-supplied `:issuer`. Returns `:error` for a malformed JWT or an
absent/blank `iss`.

# `verify`

```elixir
@spec verify(String.t(), map() | [map()], verify_opts()) ::
  {:ok, claims()} | {:error, verify_error()}
```

Verify an ID-JAG assertion against a trusted issuer JWKS and return its claims.

`trusted_jwks` is the asserting IdP's JWK set (a `%{"keys" => [...]}` map, a
bare list of JWK maps, or a single JWK map). Required opts:

  * `:issuer` - the trusted issuer the assertion's `iss` must equal.
  * `:audience` - this server's issuer identifier the assertion's `aud` must
    identify.
  * `:client_id` - the authenticated client; the `client_id` claim must equal
    it (draft §6.1).

Optional opts:

  * `:accepted_algs` - JOSE algorithms a candidate key may sign with. Defaults
    to `Attesto.SigningAlg.allowed/0` (includes RS256, which enterprise IdPs
    commonly use - unlike the FAPI request-object default).
  * `:max_lifetime_seconds` - reject an assertion whose `exp - iat` exceeds
    this bound.
  * `:now` - the verification instant (a `DateTime` or unix seconds); defaults
    to the system clock.

Returns `{:ok, claims}` (string-keyed, including the registered claims) or
`{:error, t:verify_error/0}`. The caller maps every error to `invalid_grant`.

---

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