Skip to content

WhoAmI flow isolation + scope/role authorization#29

Merged
dasiths merged 7 commits into
mainfrom
feat/whoami-flow-isolation-scopes
Jun 1, 2026
Merged

WhoAmI flow isolation + scope/role authorization#29
dasiths merged 7 commits into
mainfrom
feat/whoami-flow-isolation-scopes

Conversation

@dasiths
Copy link
Copy Markdown
Collaborator

@dasiths dasiths commented Jun 1, 2026

Summary

Refactors samples/WhoAmI so each AAuth resource access mode is an independent, self-contained pipeline, and adds a first-class demonstration of scope- and role-based authorization built on the SDK's ASP.NET Core authn/authz integration. It also adds genuine Interaction Chaining to the call-chain demo so the multi-hop flow surfaces real human consent at each hop. Spec compliance is the golden rule; backward compatibility with prior paths/response shapes is intentionally waived (alpha).

Motivation

Previously WhoAmI multiplexed all signing/access modes through one handler with in-line branching and an identity-based fallback (an agent token with no ps returned 200). That obscured how the four AAuth access modes differ and let a proof-of-possession-only token reach protected claims. This PR isolates each flow and makes the authorization model explicit and enforceable.

Separately, the interactive call-chain demo could not actually defer: when a hop lacked standing consent the SDK threw because no onInteractionRequired callback was wired, and the intermediary had no way to propagate the requirement to the user. This PR implements the spec's §Interaction Chaining so both hops surface real consent.

What changed

SDK (src/AAuth)

  • AuthTokenBuilder emits roles/groups (RFC 9068 JSON string arrays) when set; omits them when null.
  • AAuthVerificationResult exposes Roles/Groups; AAuthVerificationMiddleware parses them.
  • AAuthAuthenticationHandler maps roles to ClaimTypes.Role and groups to a new aauth:group claim.
  • AAuthScopeHandler now requires Level == Authorized and scope membership — closing the PoP-only bypass.
  • New AddAAuthRolePolicy(name, role) DI helper: requires Authorized level + the role.
  • New AAuthInteractionChainedException (carries the AAuthInteraction). An intermediary's OnInteractionRequired throws it to abort the in-flight token exchange before DeferredPoller blocks; the endpoint catches it and re-emits its own 202. The existing try/finally (no catch) propagates it cleanly — no other SDK change required.

WhoAmI sample — isolated pipelines

Each mode is its own UseWhen branch with no shared catch-all:

  • /hwk, /jkt-jwt, /jwks-uri — signature-only / identity.
  • /jwt — three-party baseline, scope whoami.
  • /jwt/admin — three-party, scope whoami:admin.
  • /jwt/roles — three-party RBAC, role whoami-admin.
  • / — unauthenticated flow index.

The identity-based fallback was removed; a PoP-only token now gets a proper 401 challenge instead of a 200.

Interaction chaining — call-chain demo (Agent → Orchestrator → WhoAmI)

  • Orchestrator: new in-memory PendingStore (id → upstream token + PS url/code). RunChainAsync builds the downstream client with WithCallChaining + a WithChallengeHandling callback that throws AAuthInteractionChainedException and declares no interaction capability (it has no user to relay to). GET / catches the chained exception, parks a pending entry, and re-emits its own 202 (own Location, pass-through PS url/code). GET /pending/{id} re-drives the hop with the stored upstream token → 202 (pending) / 200 (consent) / 403 (denied). The hop-2 auto-grant shortcut is removed.
  • SampleApp (CallChain.razor): wires both WithChallengeHandling (hop-1 PS 202) and WithInteractionHandling (hop-2 chained resource 202) into one shared handler — ChallengeHandler only acts on 401, so the chained 202 needs the interaction pipeline. The two-approval flow is now explicit: an upfront warning plus per-hop colored "Approval N of 2" badges (blue hop 1 / teal hop 2). The page no longer resets consent, so the pre-grant happy-path spec stays green. Rendered code snippets corrected to match the real implementation.

Consumers

  • MockPersonServer echoes the requested scope into the auth token, keys consent by (agent, resource, scope), publishes whoami:admin, and asserts the demo role/groups only for admin demo agents (so role denial is exercised).
  • AgentConsole, Orchestrator, SampleApp, GuidedTour updated to target /jwt and the per-mode endpoints.

Self-review remediations (Implementation Validator — "sound to merge, no blockers")

  • M1: conditional role assertion in the mock PS (admin agents only).
  • M2: added AAuthScopeHandlerTests and a /jwt/roles 403 denial integration test.
  • M3: documented that /jwt/roles challenges only for whoami and yields an unrecoverable 403 if the PS withholds the role (G7 step-up is out of scope).

Docs

  • Rewrote docs/advanced/interaction-chaining.md with the real abort/re-emit pattern (AAuthInteractionChainedException + endpoint catch) and the agent-side WithInteractionHandling wiring; cross-linked docs/workflows/call-chaining.md and docs/workflows/deferred-consent.md.
  • New docs/server/authn-authz.md covering the authN/authZ pipeline and minimal-API / classic-MVC wiring.

Testing

  • Unit: 346 pass (incl. interaction-chaining + scope/role coverage)
  • Conformance: 346 pass
  • E2E (Playwright): 22/22 pass — including a new call-chain-deferred.spec.ts that drives both consent popups to a final nested-act 200 plus a hop-1 deny path; the existing pre-grant call-chain.spec.ts stays green.
  • AgentConsole: all 6 modes return 200

Out of scope

Multi-scope AllOf/AnyOf (G6), insufficient-scope/role step-up re-challenge (G7), four-party AS pipeline, production-grade pending persistence/GC on the Orchestrator, payment/clarification capabilities, and live-infra redeploy.

Automated review triage (copilot-pull-request-reviewer)

All four inline comments were validated against the actual code path and the AAuth spec; none required a code change (3 false positives, 1 contradicted by a spec MUST). Threads replied-to and resolved.

  • AAuthAuthenticationHandler.cs null assertingIssuer (×3)false positive. The BCL Claim(type, value, valueType, issuer) ctor does not throw on a null/empty issuer; it defaults to ClaimsIdentity.DefaultIssuer (runtime-verified on net10.0). Signature-only schemes (hwk/jkt) correctly attribute claims to the local authority — no ArgumentNullException possible.
  • WhoAmI/Program.cs trusted-issuer matchinginvalid; contradicted by spec. §Identifiers mandates "Implementations MUST perform exact string comparison on server identifiers" (lowercase, no trailing slash). The current ordinal HashSet.Contains(iss) is exactly compliant; normalizing would accept spec-invalid identifiers and weaken the fail-closed allow-list.

dasiths added 6 commits June 1, 2026 16:28
- SDK: roles/groups claims, scope handler requires Authorized, AddAAuthRolePolicy
- WhoAmI: isolated per-mode pipelines + /jwt, /jwt/admin, /jwt/roles
- MockPersonServer: requested-scope echo + role/group assertion
- Consumers: point three-party calls at /jwt
- Tests: WhoAmIFlowTests updated for new flows
- Plan docs under .agent/plans
- consent.ts: grantConsent accepts optional scope param (per-hop keying)
- SampleApp call-chain.spec.ts: grant hop-1 with 'orchestrate' scope
- GuidedTour TourSession.cs: seed hop-1 consent with 'orchestrate' scope
- research.md: record AgentConsole permutation validation (all 6 pass);
  document pre-existing AP in-memory-enrollment vs disk-cache quirk
Phase 7 of whoami-flow-isolation-scopes plan:
- GuidedTour: README/UI text + step titles -> per-mode endpoints and /jwt
- SampleApp: Jwt/Deferred snippet narrative -> /jwt + scope/scheme fields
- AgentConsole: new README (signing-mode->path map, validated invocations,
  admin/roles endpoints, consent curls, enrollment-cache quirk)
- samples/README.md: WhoAmI 7-endpoint contract + updated console examples
- docs/**: AddAAuthRolePolicy, scope-requires-Authorized, roles/groups claims,
  isolated per-mode pipelines, per-endpoint scope challenges, /jwt references
…sts)

Apply maintainer-directed remediations from the Phase 8 self-review:

- M1: MockPersonServer asserts whoami-admin role/groups only for recognized
  admin demo agents (id starts with aauth:demo@), so role-based denial is
  exercised instead of every auth token carrying the role.
- M2: add AAuthScopeHandlerTests (Authorized+scope succeeds; wrong scope,
  non-Authorized level with stray scope, and missing result all denied) and
  WhoAmIFlowTests.RoleFlow_Returns403_WhenAgentLacksRole.
- M3: document that /jwt/roles challenges only for the whoami scope and yields
  an unrecoverable 403 if the PS withholds the role (G7 step-up out of scope),
  in WhoAmI Program.cs and docs/server/authorization-policies.md.

unit 333 + conformance 345 + e2e 20/20 pass.
…/authZ docs

Phase 9 — namespace PS-asserted identity by issuer and fail-closed trust:
- AAuthAuthenticationHandler stamps Claim.Issuer = asserting PS on
  NameIdentifier/Role/Group and adds composite aauth:sub_iss (iss|sub)
- AAuthVerificationMiddleware rejects auth tokens whose iss is not in
  TrustedAuthTokenIssuers (null/empty = reject all)

Phase 10 — PS-side resource-token verification (spec G9):
- TokenVerifier.VerifyResourceTokenAsync runs the 7 recipient checks via
  JWKS discovery (typ, dwk+sig, exp/iat, aud, agent, agent_jkt, approver)
- MockPersonServer /token verifies the posted resource_token and maps
  failures to invalid_resource_token / expired_resource_token (401)
- Tests: +7 TokenVerifier resource-token cases; +forged/+tampered PS cases;
  WhoAmIFlowTests negative test rewired to mutually-wired PS/WhoAmI pair

Phase 11 — docs:
- New docs/server/authn-authz.md covering the authN/authZ pipeline and
  minimal-API and classic-MVC wiring (cross-linked from index + related docs)
- token-issuance verify section, configuration keys, README/workflow fixes
Make the SampleApp call-chain demo (Agent → Orchestrator → WhoAmI)
demonstrate the spec's §Interaction Chaining end-to-end with two real
human-consent hops, replacing the broken "no onInteractionRequired
callback" deferral path.

SDK:
- Add AAuthInteractionChainedException (carries the AAuthInteraction).
  An intermediary's OnInteractionRequired throws it to abort the in-flight
  exchange before DeferredPoller blocks; the endpoint catches it and
  re-emits its own 202. The existing try/finally (no catch) propagates it
  cleanly. +3 unit tests in InteractionChainingTests.

Orchestrator:
- New in-memory PendingStore (id → upstream token + PS url/code).
- RunChainAsync builds the downstream client with WithCallChaining +
  WithChallengeHandling whose callback throws the chained exception and
  declares no "interaction" capability (no user to relay to).
- GET / catches the chained exception, parks a pending entry, and
  re-emits its OWN 202 (own Location, pass-through PS url/code).
- GET /pending/{id} re-drives the hop with the stored upstream token:
  202 (still pending) / 200 (consent) / 403 (denied). Hop-2 auto-grant
  removed.

SampleApp (CallChain.razor):
- Wire BOTH WithChallengeHandling (hop-1 PS 202) and WithInteractionHandling
  (hop-2 chained resource 202) into a shared SurfaceInteraction handler;
  ChallengeHandler only acts on 401, so the chained 202 needs the
  interaction pipeline.
- Make the two-approval flow explicit: upfront warning + per-hop colored
  "Approval N of 2" badges (blue hop 1 / teal hop 2) and hop-specific copy.
- Page no longer resets consent; consent state is controlled externally so
  the pre-grant happy-path spec stays green.
- Fix the rendered code snippets to match the real implementation (two
  agent callbacks; full Orchestrator RunChainAsync + endpoint).

Tests + docs:
- New call-chain-deferred.spec.ts drives both consent popups to a final
  nested-act 200, plus a hop-1 deny path. Existing call-chain.spec.ts
  (pre-grant) stays green.
- Rewrite docs/advanced/interaction-chaining.md with the real
  abort/re-emit pattern and agent-side WithInteractionHandling; cross-link
  call-chaining.md and deferred-consent.md.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the WhoAmI sample so each AAuth access mode is an isolated, self-contained middleware pipeline, and extends the SDK + samples to demonstrate scope-based and role-based authorization (plus interaction chaining across a multi-hop call-chain demo). It also tightens verification/authorization semantics (notably: scope enforcement requires Authorized level; PS-issued auth tokens are accepted only from explicitly trusted issuers; and MockPersonServer now verifies resource tokens via JWKS discovery).

Changes:

  • SDK: add roles/groups claims support end-to-end, add AddAAuthRolePolicy, tighten scope handler semantics, add VerifyResourceTokenAsync, and implement interaction-chaining abort via AAuthInteractionChainedException.
  • Samples: WhoAmI split into /hwk, /jkt-jwt, /jwks-uri, /jwt, /jwt/admin, /jwt/roles (+ unauthenticated index), plus Orchestrator pending-store based chained-202 re-emit and SampleApp UI wiring for two-hop consent.
  • Tests/docs: add/extend unit + integration + conformance + Playwright coverage and update documentation to match the new flows and authN/authZ wiring.
Show a summary per file
File Description
tests/e2e/helpers/consent.ts Extend consent helper to optionally include scope.
tests/AAuth.Tests/Tokens/TokenVerifierTests.cs Add unit tests for VerifyResourceTokenAsync (happy + failure cases).
tests/AAuth.Tests/Server/AAuthScopeHandlerTests.cs New unit tests for scope policy requiring Authorized level + exact scope membership.
tests/AAuth.Tests/Integration/WhoAmIFlowTests.cs Update integration coverage to new WhoAmI endpoints, trusted-issuer behavior, roles flow, and scope in resource token.
tests/AAuth.Tests/Integration/MockPersonServerTests.cs Add in-process resource discovery stub and tests for rejecting forged/tampered resource tokens.
tests/AAuth.Tests/Agent/InteractionChainingTests.cs New unit tests for aborting deferred polling via AAuthInteractionChainedException.
tests/AAuth.Conformance/Observability/ActivityDiagnosticsTests.cs Set TrustedAuthTokenIssuers in conformance harness options.
tests/AAuth.Conformance/HttpSignatures/VerificationMiddlewareTests.cs Set TrustedAuthTokenIssuers in verification middleware conformance tests + add fail-closed test.
tests/AAuth.Conformance/HttpSignatures/ChallengeMiddlewareTests.cs Set TrustedAuthTokenIssuers in challenge middleware conformance host.
tests/AAuth.Conformance/HttpSignatures/AuthorizationIntegrationTests.cs Set trusted issuers; assert (iss, sub) namespacing surfaced via claims.
tests/AAuth.Conformance/CallChaining/UseAAuthIntermediaryTests.cs Configure trusted issuers for intermediary verification.
src/AAuth/Tokens/TokenVerifier.cs Add VerifyResourceTokenAsync implementing resource-token verification via issuer metadata/JWKS.
src/AAuth/Tokens/AuthTokenBuilder.cs Add Roles/Groups and emit RFC 9068 JSON string arrays when present.
src/AAuth/Server/AAuthVerificationResult.cs Add Roles/Groups to verification result feature.
src/AAuth/Server/AAuthVerificationOptions.cs Document fail-closed behavior for trusted auth-token issuers.
src/AAuth/Server/AAuthVerificationMiddleware.cs Parse roles/groups; enforce fail-closed trusted-issuer allowlist for auth tokens.
src/AAuth/Server/AAuthScopeHandler.cs Require Level == Authorized in addition to scope membership.
src/AAuth/Server/AAuthAuthenticationHandler.cs Map roles/groups into claims; namespace identity claims by asserting issuer.
src/AAuth/DependencyInjection/AAuthResourceServiceCollectionExtensions.cs Add AddAAuthRolePolicy helper.
src/AAuth/Agent/AAuthInteractionExceptions.cs Add AAuthInteractionChainedException for interaction chaining abort/re-emit pattern.
samples/WhoAmI/Program.cs Split into per-mode verification/challenge branches; add scope + role policies; add flow index; remove identity fallback.
samples/SampleApp/playwright-tests/call-chain.spec.ts Pre-grant correct hop-1 scope consent for orchestrator.
samples/SampleApp/playwright-tests/call-chain-deferred.spec.ts New E2E spec for two-hop deferred consent + chained interaction flow.
samples/SampleApp/Components/Pages/Jwt.razor Update rendered snippets/requests to /jwt path and correct keystore usage.
samples/SampleApp/Components/Pages/Deferred.razor Update rendered snippets/requests to /jwt path.
samples/SampleApp/Components/Pages/CallChain.razor Wire both challenge-handling and interaction-handling callbacks; UI updates for two approvals.
samples/README.md Update WhoAmI sample description and documented endpoints.
samples/Orchestrator/Program.cs Add interaction chaining: pending store + re-emit 202 + poll endpoint; enforce trusted PS issuer.
samples/Orchestrator/PendingStore.cs New in-memory pending store for chained interaction flow.
samples/MockPersonServer/README.md Update docs: PS now verifies resource tokens using SDK helper.
samples/MockPersonServer/Program.cs Verify resource tokens via VerifyResourceTokenAsync; echo requested scope; conditionally assert roles/groups; scope-keyed consent.
samples/GuidedTour/TourSession.cs Update tour steps/paths; seed orchestrate scope consent for call-chain mode.
samples/GuidedTour/README.md Update documentation for new WhoAmI endpoint layout and /jwt three-party path.
samples/GuidedTour/playwright-tests/identity.spec.ts Update step text assertions for new identity flow wording.
samples/GuidedTour/playwright-tests/deferred.spec.ts Update step text assertion to /jwt.
samples/GuidedTour/playwright-tests/autonomous.spec.ts Update step text assertion to /jwt.
samples/GuidedTour/Components/Pages/Tour.razor Update identity flow explanation to refer to per-mode endpoints.
samples/AgentConsole/README.md New README documenting behavior, mode→path mapping, and consent instructions.
samples/AgentConsole/Program.cs Update default jwt path mapping from / to /jwt.
docs/workflows/ps-asserted-access.md Document PS resource-token verification in the workflow.
docs/workflows/deferred-consent.md Add note about PS resource-token verification and link interaction chaining.
docs/workflows/call-chaining.md Update WhoAmI downstream path to /jwt and link interaction chaining docs.
docs/server/verification-middleware.md Document fail-closed trusted-issuer allowlist and roles/groups surfacing.
docs/server/token-issuance.md Add section documenting PS-side resource-token verification API + checks.
docs/server/challenge-middleware.md Add per-endpoint scope challenge branching guidance.
docs/server/authorization-policies.md Expand docs to cover scope+role policies, level semantics, and namespacing.
docs/server/authn-authz.md New doc describing full authN/authZ pipeline and wiring for minimal APIs + MVC.
docs/reference/dependency-injection.md Document authN/authZ registrations and new role policy helper.
docs/reference/configuration.md Document fail-closed trusted-issuer behavior and sample config keys.
docs/README.md Link to new authN/authZ doc.
docs/advanced/interaction-chaining.md Rewrite with abort/re-emit pattern using AAuthInteractionChainedException.
.agent/plans/2026-06-01-whoami-flow-isolation-scopes/research.md New research doc for the initiative.
.agent/plans/2026-06-01-whoami-flow-isolation-scopes/implementation-plan.md New phased implementation plan with DoD checklists.

Copilot's findings

  • Files reviewed: 53/53 changed files
  • Comments generated: 8

Comment thread src/AAuth/Server/AAuthAuthenticationHandler.cs
Comment thread src/AAuth/Server/AAuthAuthenticationHandler.cs
Comment thread src/AAuth/Server/AAuthAuthenticationHandler.cs
Comment thread samples/WhoAmI/Program.cs
Comment thread src/AAuth/Server/AAuthAuthenticationHandler.cs
Comment thread src/AAuth/Server/AAuthAuthenticationHandler.cs
Comment thread src/AAuth/Server/AAuthAuthenticationHandler.cs
Comment thread samples/WhoAmI/Program.cs
Add the research and implementation plan documents for the call-chain
interaction-chaining work (Phases 2-5) that were omitted from the
initial feature commit.
@dasiths dasiths merged commit 27a4f21 into main Jun 1, 2026
2 checks passed
@dasiths dasiths deleted the feat/whoami-flow-isolation-scopes branch June 1, 2026 21:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants