Transport for the host-private authorization state carried by an authorization code.
Attesto.AuthorizationCode fixes the canonical grant :data map at exactly
nine keys and rejects a persisted record carrying any sibling key, so
host-specific values belong inside :claims. This module owns the single
reserved claims key that carries the host's :authorization_code_private_context
value from the bundled authorization endpoint to the token endpoint, and the
rules that keep it from escaping.
The value is:
- validated as a portable JSON object (
Attesto.Claims.portable_json_object?/1) both alone and after nesting under the reserved claims key, because the complete claims map round-trips a JSONB column and must survive it unchanged; - bounded at 4096 bytes once encoded, so a host cannot grow an authorization code without limit;
- removed from the redeemed grant's claims by
pop/1before principal construction, so it can never be read as an OIDC claim or minted into a token.
The reserved key is namespaced and is refused as host input: a host that
stashes its own value under it at the bundled authorization endpoint would
otherwise be able to forge private context for the completion callback. A
custom authorization-code issuer or custom reconstruction path that accepts
request-derived claims MUST reject claims_key/0 in those claims; use
reserved?/1 for that check before reconstructing or issuing a code.
Summary
Functions
The reserved claims key carrying private context.
The maximum encoded size, in bytes, of a private-context value.
Removes the reserved key from claims, returning the private context and the
cleaned claims.
Folds private_context into claims under the reserved key.
Rejects a host-supplied claims map that already carries the reserved key.
Types
@type t() :: map()
The host's private authorization state.
Functions
@spec claims_key() :: String.t()
The reserved claims key carrying private context.
@spec max_encoded_bytes() :: pos_integer()
The maximum encoded size, in bytes, of a private-context value.
Removes the reserved key from claims, returning the private context and the
cleaned claims.
Returns {nil, claims} when the code carries none. A value stored under the
reserved key that is not a map is reported as :invalid, so a tampered or
malformed row fails closed at the token endpoint instead of being handed to
the completion callback as valid state.
@spec put(map(), t() | nil) :: {:ok, map()} | {:error, :invalid_private_context | :private_context_too_large | :invalid_code_claims}
Folds private_context into claims under the reserved key.
nil stores nothing, leaving claims untouched. A value that is not a
portable JSON object by itself or after nesting under the reserved key, or
that exceeds max_encoded_bytes/0 once encoded, is rejected rather than
truncated or silently dropped — a host that cannot persist its policy state
must not get a code that looks like it carries it. A malformed private value
returns :invalid_private_context; a valid private value combined with
non-portable code claims returns :invalid_code_claims.
Rejects a host-supplied claims map that already carries the reserved key.
The bundled authorization endpoint calls this before private context is
folded in, so the reserved key is only ever written by this library. Custom
authorization-code issuers and reconstruction paths that accept
request-derived claims MUST call this helper and reject true before
issuing or reconstructing a code.