AttestoPhoenix.AuthorizationCodePrivateContext (AttestoPhoenix v3.2.1)

Copy Markdown View Source

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/1 before 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

Types

t()

The host's private authorization state.

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

t()

@type t() :: map()

The host's private authorization state.

Functions

claims_key()

@spec claims_key() :: String.t()

The reserved claims key carrying private context.

max_encoded_bytes()

@spec max_encoded_bytes() :: pos_integer()

The maximum encoded size, in bytes, of a private-context value.

pop(claims)

@spec pop(map()) :: {t() | nil | :invalid, map()}

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.

put(claims, private_context)

@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.

reserved?(claims)

@spec reserved?(map()) :: boolean()

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.