Attesto.PrincipalKind (Attesto v0.5.0)

Copy Markdown View Source

One kind of subject a token can describe.

A single issuer and verifier can serve several kinds of principal - a machine client authenticating with client_credentials, a human whose dashboard session was exchanged for a token, a device. They share every standard claim (iss, aud, exp, sub, scope, …) and differ only in three policy-defined ways, which this struct captures:

  • claim_value - the value carried in the configured principal-kind claim (see Attesto.Config). A token's kind is read from this claim and cross-checked, so a token can never be silently routed down the wrong principal path.

  • sub_prefix - the namespace prefix the sub claim MUST carry for this kind (e.g. "oc_" for a client, "usr_" for a user). The verifier requires sub to start with the prefix of the kind named in the principal-kind claim; a mismatch fails verification. This is defense-in-depth against a token whose kind claim and subject disagree.

  • required_claims - extra claims this kind MUST carry, each with a shape. A client carries client_id; a user-session token might carry an acting-account id, a session id, and a token-version counter for bulk revocation. The verifier (and the minter) reject a token of this kind that is missing one, or whose value has the wrong shape, so downstream code never branches on a nil it assumed was present.

Attesto does not interpret what a kind means - it only enforces the cross-checks. The meaning is the host application's policy.

Required-claim shapes

Each entry in required_claims is {claim_name, shape} where shape is one of:

  • :non_empty_string - a binary that is not "".
  • :string - any binary (may be empty).
  • :non_neg_integer - an integer >= 0.

Summary

Functions

Returns :ok if claims carries every required claim for this kind with the correct shape, or {:error, {claim_name, :missing | :wrong_shape}} on the first violation.

Types

shape()

@type shape() :: :non_empty_string | :string | :non_neg_integer

t()

@type t() :: %Attesto.PrincipalKind{
  claim_value: String.t(),
  required_claims: [{String.t(), shape()}],
  sub_prefix: String.t()
}

Functions

check_required(principal_kind, claims)

@spec check_required(t(), %{optional(String.t()) => term()}) ::
  :ok | {:error, {String.t(), :missing | :wrong_shape}}

Returns :ok if claims carries every required claim for this kind with the correct shape, or {:error, {claim_name, :missing | :wrong_shape}} on the first violation.

new(claim_value, sub_prefix, opts \\ [])

@spec new(String.t(), String.t(), keyword()) :: t()

Build a principal kind.

Attesto.PrincipalKind.new("client", "oc_",
  required_claims: [{"client_id", :non_empty_string}]
)

Raises ArgumentError if claim_value/sub_prefix are not non-empty binaries or any required-claim shape is unknown - this is configuration, evaluated once at boot, so a malformed kind should fail loudly rather than at the first token operation.