Completes FLEX-WP-0005 T05 and closes the Foundations and Topaz Alignment workstream. docs/iam-profile-consumption.md captures flex-auth's input surface against NetKingdom IAM Profile v0.1: - boundary (flex-auth consumes verified claims; upstream layer validates signatures and audiences) - normalized input envelope (matches Markitect's EnterpriseIdentity) - required, recommended, and tolerated claim variations - role-claim location union (top-level / realm_access / resource_access) - scope encoding (string vs array) - principal-type detection (human / service / emergency) - group-overage and freshness expectations - production vs local-development handling examples/claims/ ships five contract fixtures: - key-cape-lightweight.yaml (profile minimum) - keycloak-heavy.yaml (full variation set + MFA) - service-account.yaml (svc-* hub-to-hub) - emergency.yaml (break-glass with incident metadata) - keycloak-group-overage.yaml (Entra-style hasgroups: true) All fixtures parse as valid YAML. They become contract tests for the standalone evaluator (FLEX-WP-0002 P2.4) and the Topaz adapter (FLEX-WP-0004 T01); both code paths must produce identical normalized envelopes for the same fixture. FLEX-WP-0005 workstream marked status=done in this file and completed in the State Hub. FLEX-WP-0002 is now fully unblocked. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
7.2 KiB
NetKingdom IAM Profile — flex-auth Consumption Surface
Date: 2026-05-16
Status: Draft for FLEX-WP-0005 P5.5; binds the input contract for the
standalone evaluator (FLEX-WP-0002) and every PDP adapter (FLEX-WP-0004).
Upstream: ~/the-custodian/canon/standards/iam-profile_v0.1.md.
Boundary
The NetKingdom IAM Profile defines the OIDC contract shared across hubs and services. flex-auth consumes verified claims; it does not verify token signatures, fetch JWKS, or terminate OIDC sessions. Those responsibilities belong upstream:
- key-cape (lightweight mode) validates tokens against its local OIDC provider and emits claims that conform to the profile.
- Keycloak (heavy mode) signs tokens; integration code (e.g.
Markitect's
NetKingdomIdentityClaimsAdapter) validates issuer, audience, signature, expiry, and clock skew before handing claims to flex-auth.
A flex-auth deployment that exposes a network endpoint MUST be fronted by an identity layer that does the verification. The flex-auth core accepts a normalized claim envelope and is responsible for everything after "this caller is authenticated".
Input Envelope
flex-auth's standalone evaluator and adapters consume a normalized
envelope identical to Markitect's EnterpriseIdentity shape:
issuer: <oidc issuer URL> # required
subject: <stable subject id> # required
principal_type: human | service | emergency
audience: [<aud>, ...] # required, non-empty
authorized_party: <azp or client_id, optional>
preferred_username: <string> # required for humans
roles: [<role>, ...] # required, non-empty
scopes: [<scope>, ...] # required, non-empty
groups: [<group>, ...] # optional; resolved by directory layer
assurance:
acr: <oidc acr value, optional>
amr: [<oidc amr value>, ...] # e.g. pwd, otp, mfa, hwk
mfa: <bool — derived from amr>
directory:
groups_claim_present: <bool>
group_overage: <bool> # Microsoft Entra-style group overage
claims: { ... } # full original claim map (minus 'groups')
provenance:
source: claims | jwt | jwt-fixture
verified_signature: <bool>
This is the envelope every check API call receives, regardless of which upstream identity provider produced the token.
Required Claims (per IAM Profile §"Required Claims")
flex-auth treats the following as hard requirements. Missing any
produces a validation_error before the request reaches a policy
package.
| Claim | flex-auth field | Notes |
|---|---|---|
iss |
issuer |
Must match the deployment's expected issuer; production rejects local-dev issuers (localhost, 127.0.0.1, .local, dev.local). |
sub |
subject |
Stable identifier; not a username. |
aud |
audience |
Must include the flex-auth instance or the protected system. |
exp |
(validated upstream) | flex-auth tolerates ≤60s clock skew per profile §"Token Lifecycle". |
iat |
(validated upstream) | Same. |
scope or scp |
scopes |
At least one scope required. Empty scope is a hard fail. |
preferred_username |
preferred_username |
Required for principal_type=human. Optional for service accounts. |
roles or realm_access.roles or resource_access.<client>.roles |
roles |
Union of all three sources. At least one role required. |
Recommended Claims
| Claim | flex-auth field | Use |
|---|---|---|
email |
claims.email |
Contact identity; never used for authorization decisions. |
name |
claims.name |
Display only. |
groups |
groups (after resolution) |
Authorization input; subject to freshness/overage. |
azp |
authorized_party |
Distinguishes service-account client from impersonating client. |
acr |
assurance.acr |
Authentication context class; gates high-trust scopes. |
amr |
assurance.amr |
Authentication methods; otp/mfa/hwk lift assurance.mfa to true. |
Tolerated Variations
flex-auth normalizes — protected systems never see the variation.
- Role claim location. Three OIDC providers ship roles in three
places: top-level
roles, Keycloak'srealm_access.roles, and Keycloak's per-clientresource_access.<client>.roles. flex-auth unions all three. - Scope encoding.
scope(space-separated string) andscp(array) both accepted; both produce the samescopesarray. - Audience encoding.
audas a single string or as an array; flex-auth always normalizes to an array. - MFA signal. Either an explicit
mfa: trueclaim or any ofotp/mfa/hwkinamrproducesassurance.mfa = true.
Principal-Type Detection
flex-auth classifies the principal by:
- If
client_idis set andserviceis inroles→service. - If
azpstarts withsvc-orserviceis inroles→service. - If
emergencyis inroles→emergency. - Otherwise →
human.
This matches Markitect's NetKingdomIdentityClaimsAdapter._principal_type
and follows IAM Profile §"Hub-to-Hub Service Account Pattern" (service
accounts named svc-* and carrying the service role).
Group Overage and Freshness
Microsoft Entra and Keycloak both clip the groups claim once a
threshold is reached; the token then carries hasgroups: true (Entra)
or _claim_names.groups (also Entra). flex-auth's directory layer is
responsible for resolving the full set via Graph/SCIM/Keycloak admin
API; the claim envelope carries directory.group_overage = true so
policy packages can decide whether to fail-closed or accept the
partial set with an audit_only outcome.
Group freshness is tracked at the directory-resolver layer (out of scope for this document; see FLEX-WP-0004 T05).
Production vs Local Development
Per IAM Profile §"Local Development Profile":
- Local-development issuers (
localhost,127.0.0.1, hostnames ending in.local,dev.local) are rejected whenenvironment=productionis set in the request context. - A development token marked clearly through issuer/audience is accepted in non-production environments.
- The local-development path exists to keep flex-auth useful before Keycloak is wired in; it never weakens production rules.
Emergency Principals
Per IAM Profile §"Human Override and Emergency Access":
emergencyis a first-classprincipal_type.- Every decision involving an emergency principal MUST record a
record_emergencyobligation in the decision envelope. - Policy packages MAY allow emergency principals; flex-auth's audit layer ensures the action is durable regardless.
Reference Implementation
Markitect's NetKingdomIdentityClaimsAdapter (at
markitect-tool/src/markitect_tool/policy/enterprise.py) implements
the validation steps above in Python. flex-auth's Go implementation
(FLEX-WP-0002 P2.4) mirrors its behavior and stays in sync via
contract tests against the fixtures in examples/claims/.
Open Items
- Whether
rolesbecomes canonical andrealm_access.rolesbecomes legacy is still listed as an open question in IAM Profile v0.1. As of 2026-05-16 flex-auth normalizes both with no preference. Workload identity(Kubernetes service-account tokens, GCP/AWS workload-identity federation) is not yet in the IAM Profile. flex-auth's service-account handling is currently OIDC-only.