AttestoPhoenix.AuthorizationServer.JwtBearer (AttestoPhoenix v0.11.0)

Copy Markdown View Source

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

A token request arrives with grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer and an assertion parameter carrying an ID-JAG: a short-lived JWT the enterprise IdP signed (after its own RFC 8693 token exchange) asserting one user for this resource application. authorize/3 turns that assertion into the local subject and scope ceiling the token endpoint mints from. It owns the stateful concerns that Attesto.IdentityAssertion (conn-free, pure) deliberately leaves out:

  • issuer trust - the assertion's iss must be a configured trusted issuer (jwt_bearer: [issuers: %{...}]); an unconfigured issuer is denied without revealing which issuers are trusted.
  • JWKS resolution - static keys, a cached jwks_uri fetch (reusing the SSRF-guarded CIMD fetcher + cache), or a custom :jwks_resolver.
  • jti replay - via the configured :replay_check (the same seam DPoP uses), namespaced so an ID-JAG jti never collides with a DPoP proof's.
  • subject resolution - the host's :resolve_jwt_bearer_subject callback maps the validated claims to a local principal subject (or denies).

Every failure returns {:error, atom}; the token core (AttestoPhoenix.AuthorizationServer.Token) maps a missing assertion parameter to RFC 6749 §5.2 invalid_request and every assertion/trust/replay/ subject failure to invalid_grant, as the draft requires.

This is NOT private_key_jwt client authentication (RFC 7523 §3) nor the RFC 8693 token-exchange grant (which runs at the IdP).

Summary

Types

The resolved local subject, the assertion's scope ceiling (nil when the assertion carried no scope claim, so the host policy alone decides), and the validated claims.

Functions

Validate the ID-JAG assertion and resolve the local subject.

Types

error()

@type error() ::
  :missing_assertion
  | :untrusted_issuer
  | :jwks_unavailable
  | :invalid_assertion
  | :replay
  | :subject_denied

result()

@type result() :: %{
  subject: String.t(),
  scope_ceiling: [String.t()] | nil,
  claims: Attesto.IdentityAssertion.claims()
}

The resolved local subject, the assertion's scope ceiling (nil when the assertion carried no scope claim, so the host policy alone decides), and the validated claims.

Functions

authorize(config, client_id, params)

@spec authorize(AttestoPhoenix.Config.t(), String.t() | nil, map()) ::
  {:ok, result()} | {:error, error()}

Validate the ID-JAG assertion and resolve the local subject.

client_id is the already-authenticated client's identifier (the token endpoint resolved it from client authentication); the assertion's client_id claim MUST equal it. Returns {:ok, %{subject, scope_ceiling, claims}} or {:error, t:error/0}.