# NetKingdom IAM Profile — flex-auth Consumption Surface Date: 2026-05-22 Status: Aligned with NetKingdom IAM Profile v0.2; binds the input contract for the standalone evaluator (FLEX-WP-0002) and every PDP adapter (FLEX-WP-0004). Upstream: `~/net-kingdom/canon/standards/iam-profile_v0.2.md`. ## Boundary The NetKingdom IAM Profile defines the OIDC contract shared across platform, tenant, service, and agent principals. 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: ```yaml issuer: # required subject: # required tenant: tenant:platform | tenant: # required principal_type: human | service | agent audience: [, ...] # required, non-empty authorized_party: preferred_username: # required for humans roles: [, ...] # required, non-empty scopes: [, ...] # required, non-empty groups: [, ...] # required, may be empty assurance: level: aal0 | aal1 | aal2 | aal3 | break_glass methods: [, ...] mfa: source: at: acr: amr: [, ...] # tolerated provider-native input agent: id: mode: autonomous | delegated directory: groups_claim_present: group_overage: # Microsoft Entra-style group overage claims: { ... } # full original claim map (minus 'groups') provenance: source: claims | jwt | jwt-fixture verified_signature: ``` This is the envelope every check API call receives, regardless of which upstream identity provider produced the token. ## Required Claims (per IAM Profile v0.2 "Core 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. | | `tenant` | `tenant` | Required for platform/tenant boundary decisions. | | `principal_type` | `principal_type` | `human`, `service`, or `agent`; emergency is a role plus `assurance.level=break_glass`. | | `groups` | `groups` | Required, possibly empty; overage is handled by directory resolvers. | | `scope` or `scp` | `scopes` | At least one scope required. Empty scope is a hard fail. | | `roles` | `roles` | Canonical role source. At least one role required by current flex-auth policy fixtures. | | `assurance` | `assurance` | Required normalized evidence object with level, methods, mfa, and source. | | `preferred_username` | `preferred_username` | Required for `principal_type=human`. Optional for service and agent principals. | ## Recommended Claims | Claim | flex-auth field | Use | | --- | --- | --- | | `email` | `claims.email` | Contact identity; **never** used for authorization decisions. | | `name` | `claims.name` | Display only. | | `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.** IAM Profile v0.2 makes top-level `roles` canonical. During migration, flex-auth may also accept Keycloak's `realm_access.roles` and `resource_access..roles`, but those are provider-native compatibility inputs. - **Scope encoding.** `scope` (space-separated string) and `scp` (array) both accepted; both produce the same `scopes` array. - **Audience encoding.** `aud` as a single string or as an array; flex-auth always normalizes to an array. - **MFA signal.** IAM Profile v0.2 uses `assurance.mfa` and `assurance.level`. Legacy/provider-native `amr` and `acr` are tolerated as inputs to the normalized assurance object. ## Principal-Type Detection IAM Profile v0.2 supplies `principal_type` directly. flex-auth uses that claim as normative input. Legacy fixtures may be classified by: 1. If `client_id` is set and `service` is in `roles` → `service`. 2. If `azp` starts with `svc-` or `service` is in `roles` → `service`. 3. If agent metadata is present → `agent`. 4. Otherwise → `human`. This matches Markitect's `NetKingdomIdentityClaimsAdapter._principal_type` as a compatibility path. New claim envelopes should not force flex-auth to infer principal type. ## 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 when `environment=production` is 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": - Emergency access is represented as a human, service, or agent principal with an `emergency`/`break-glass` role and `assurance.level: break_glass`. - Every decision involving an emergency principal MUST record a `record_emergency` obligation 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/`. ## Compatibility Notes - `roles` is canonical in IAM Profile v0.2. `realm_access.roles` and `resource_access..roles` remain tolerated provider-native inputs while Keycloak mappings are updated. - Workload identity may enter through a documented token-exchange path, but the normalized envelope still carries `principal_type: service` or `principal_type: agent`, `tenant`, and `assurance`.