skip to content

In OpenID Federation 1.0, how does a Trust Anchor's metadata_policy change the metadata a member entity below it gets?

level: seniorimportance: should knowfreq 32%

answer

  1. declared metadata is input, not output
  2. policy rides only in Subordinate Statements
  3. narrow going down, never widen
  4. seven operators, two easily confused pairs
  5. unknown crit operator means reject

basics

~20 s

The member's declared metadata is input, not output. Policy operators carried in Subordinate Statements merge down the Trust Chain and are applied to it, so what every verifier acts on is the resolved result, which can be narrower than what the member published.

solid answer

~40 s

`metadata_policy` appears only in **Subordinate Statements**, keyed by Entity Type Identifier — `openid_relying_party`, `openid_provider` and the rest — and then by metadata parameter. Its operators are `value` (force this), `add` (append to a list), `default` (supply when absent), `one_of` (restrict a scalar to a set), `subset_of` and `superset_of` (restrict a list), and `essential` (the parameter must end up present). Policies from every Subordinate Statement in the chain are **merged from the Trust Anchor downward**: an Intermediate may narrow what its Superior set, never relax it, and a merge that cannot be satisfied is a resolution failure rather than a silent choice. `metadata_policy_crit` names operators a consumer must understand; meeting one it does not implement means rejecting the statement, not skipping the operator.

code

json · 19 lines
json
{
  "iss": "https://anchor.schools.example",
  "sub": "https://trust-a.schools.example",
  "iat": 1758240000,
  "exp": 1758844800,
  "jwks": { "keys": [ { "kty": "EC", "kid": "ta-2026a", "crv": "P-256", "x": "f83O…", "y": "x_FE…" } ] },
  "metadata_policy": {
    "openid_relying_party": {
      "grant_types": { "subset_of": [ "authorization_code", "refresh_token" ] },
      "id_token_signed_response_alg": { "one_of": [ "RS256", "ES256" ] },
      "contacts": { "add": [ "[email protected]" ] },
      "policy_uri": { "essential": true }
    }
  },
  "constraints": {
    "max_path_length": 1,
    "allowed_entity_types": [ "openid_relying_party" ]
  }
}

go deeper

for a junior

Recall that in a federation a member does not have the last word on its own settings — the party that vouches for it can narrow what those settings resolve to.

for a middle

Explain the operators and the two confusable pairs: add versus superset_of, and default versus value, and say which of them can make resolution fail.

for a senior

Diagnose with it. When declared and observed behaviour disagree and nothing errors, resolve the chain and read the merged policy level by level before suspecting either end.

for a principal

Decide how much to encode as policy. Every rule you enforce here is one members cannot vary without you, and every rule you leave to contract is one your software will not catch.

## Declared metadata is an input, not an answer An entity describes itself in the `metadata` claim of its **Entity Configuration**. In a multilateral federation that description is a *request*: the operator that vouches for the entity also decides what the description is allowed to resolve to. That decision travels as `metadata_policy`, carried **only** in the **Subordinate Statements** a Superior issues, never in the entity's own configuration — which is precisely what stops a member granting itself latitude the federation withheld. Policy is keyed in two levels: first by **Entity Type Identifier** (`openid_relying_party`, `openid_provider`, `oauth_authorization_server`, `oauth_client`, `oauth_resource`, `federation_entity`), then by the metadata parameter it governs. ## The operators | Operator | Effect on the parameter | |---|---| | `value` | Force it to exactly this value, whatever the entity declared | | `add` | Add these values to the entity's list | | `default` | Use this when the entity declared nothing | | `one_of` | The resulting scalar must be one of these | | `subset_of` | The resulting list may contain only these | | `superset_of` | The resulting list must contain at least these | | `essential` | The parameter must be present in the result | Read the list carefully, because two pairs are routinely confused. `add` and `superset_of` both concern values being present, but `add` *puts them there* and `superset_of` *requires them to be there*, failing otherwise. `default` and `value` both supply a value, but `default` yields to what the entity declared and `value` overrides it. ## Merging down the chain A chain can carry policy at several levels: the Trust Anchor's statement about an Intermediate, that Intermediate's statement about the next one down, and so on. Those policies are merged, top-down, before being applied to the leaf's declared metadata. The merge is directional. A Superior nearer the anchor sets the outer bound, and a Superior below it may narrow that bound further — intersecting a `subset_of` with a smaller set, say — but may not widen it. Where two levels cannot be reconciled at all, the correct outcome is a **resolution failure**: the chain does not resolve, rather than resolving to whichever level the implementation happened to apply last. Silent precedence would let an Intermediate quietly undo the anchor's rule, and the whole point of anchoring trust is that it cannot. `metadata_policy_crit` exists for the same reason at the operator level. It lists policy operators that a consumer **must** understand; if a consumer meets one there that it does not implement, it must reject the statement rather than ignore that operator and resolve the rest. An unimplemented restriction silently skipped is worse than no restriction at all, because everyone downstream believes it applied. ## The failure this produces in the field The schools-network version goes like this. An academy trust registers a relying party declaring three grant types. The network's Trust Anchor carries `"grant_types": { "subset_of": [...] }` naming two of them. Every provider in the federation resolves the member's metadata to those two, and the third simply is not there. Nothing errors at registration, because nothing was rejected — the value was narrowed, exactly as designed. The member's operators report that "the registration was ignored", and look for their bug in their own configuration for a week. The diagnostic habit worth building: when a member's declared metadata and the behaviour everyone else sees disagree, resolve the chain yourself and read the merged policy, level by level, before touching either end. ## constraints is a different lever `constraints` also travels in Subordinate Statements and is easy to file mentally under the same heading, but it governs the **shape of the subtree**, not the value of any metadata parameter: - `max_path_length` — how many Intermediates may sit between this Superior and a Leaf Entity; - `naming_constraints` — which Entity Identifiers are permitted or excluded beneath it; - `allowed_entity_types` — which Entity Type Identifiers may appear below it. Policy decides what a member may declare. Constraints decide what may exist below a Superior at all.

  • How does constraints differ from metadata_policy?
    `metadata_policy` governs the values of metadata parameters for entities below a Superior. `constraints` governs the subtree's shape: `max_path_length` limits how many Intermediates may sit between that Superior and a Leaf Entity, `naming_constraints` limits which Entity Identifiers may appear beneath it, and `allowed_entity_types` limits which Entity Type Identifiers may. One decides what a member may say about itself, the other what may exist at all.
  • Two Superiors in the chain set policies that cannot both be satisfied — what should happen?
    The chain fails to resolve. Merging is directional — a Superior below may narrow what one nearer the anchor set, never relax it — so an irreconcilable pair is a defect in the federation's configuration. Picking a winner silently would let an Intermediate override the anchor, which defeats the purpose of anchoring the trust in the first place.
  • What is metadata_policy_crit for?
    It names policy operators that a consumer must understand in order to use the statement. If a consumer encounters an operator listed there that it does not implement, it rejects the statement rather than resolving the rest and ignoring that operator. Silently skipping a restriction is the dangerous outcome, because every party downstream assumes it was applied.
  • Where do you look first when a member's declared metadata and observed behaviour disagree?
    At the resolved metadata, then at the merged policy level by level. The most common cause is not a bug at either end but a narrowing operator — `subset_of` or `one_of` — that removed the value in question during resolution, which produces no error anywhere because nothing was rejected.

saying these in an interview costs you the question

  • Thinks a member's declared metadata always beats the federation's policy
  • Believes an Intermediate may relax a policy its Superior set
  • Assumes an operator listed in metadata_policy_crit can be skipped if unsupported
  • Puts metadata_policy in the entity's own Entity Configuration
  • Reads subset_of as adding the listed values to the result
  • Confuses constraints on the subtree with policy on metadata values