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
issmust 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_urifetch (reusing the SSRF-guarded CIMD fetcher + cache), or a custom:jwks_resolver. jtireplay - via the configured:replay_check(the same seam DPoP uses), namespaced so an ID-JAGjtinever collides with a DPoP proof's.- subject resolution - the host's
:resolve_jwt_bearer_subjectcallback 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
@type error() ::
:missing_assertion
| :untrusted_issuer
| :jwks_unavailable
| :invalid_assertion
| :replay
| :subject_denied
@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
@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}.