# `AttestoPhoenix.AuthorizationCodePrivateContext`
[🔗](https://github.com/XukuLLC/attesto_phoenix/blob/v4.2.1/lib/attesto_phoenix/authorization_code_private_context.ex#L1)

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.

# `t`

```elixir
@type t() :: map()
```

The host's private authorization state.

# `claims_key`

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

The reserved claims key carrying private context.

# `max_encoded_bytes`

```elixir
@spec max_encoded_bytes() :: pos_integer()
```

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

# `pop`

```elixir
@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`

```elixir
@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?`

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

---

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