Skip to content

feat: four-party federated access with stub and Keycloak Access Servers#30

Merged
dasiths merged 24 commits into
mainfrom
feat/four-party-federated-access
Jun 3, 2026
Merged

feat: four-party federated access with stub and Keycloak Access Servers#30
dasiths merged 24 commits into
mainfrom
feat/four-party-federated-access

Conversation

@dasiths
Copy link
Copy Markdown
Collaborator

@dasiths dasiths commented Jun 3, 2026

Summary

Adds the four-party federated access flow (Agent → Resource → Person Server → Access Server) to the SDK and samples, with both a pure-.NET stub Access Server and a Keycloak-backed Access Server, plus interactive consent that "bubbles up" from the AS to the agent.

From the agent's perspective the flow is byte-for-byte identical to PS-asserted access — the PS↔AS federation (and any AS interaction) is transparent. The discriminator is the resource token's aud: when it points at an Access Server, the PS federates instead of asserting access itself.

What's included

SDK (src/AAuth/)

  • AccessServerClient.FederateAsync — signed PS→AS token exchange that drives the full AS lifecycle (202 → interaction/claims → poll → verified aa-auth+jwt).
  • MapAAuthAccessServer host helper: /.well-known/aauth-access.json + JWKS, RFC 9421 request-signature verification (pinned to trusted Person Servers), token verification, pluggable IAccessPolicy, and IAccessPendingStore for deferred decisions.
  • IAccessPolicy (Allow/Deny/NeedsInteraction/NeedsClaims/NeedsPayment), claims-push API (requirement=claims), and dwk=aauth-access.json minting.

Samples

  • MockAccessServer with config-selected stub and keycloak policies; the stub renders its own interactive Approve/Deny consent screen (AccessServer:RequireConsent).
  • Four-party page in SampleApp and a Federated flow in GuidedTour (provider-neutral labels — stub and Keycloak look identical to the agent).
  • Consolidated demos: make demo (stub AS, no Docker) and make demo-keycloak (live Keycloak), each launching both UIs and printing a grouped URL summary.

Tests

  • Integration tests for the AS, the Keycloak adapter, and PS federation.
  • Playwright e2e covering the federated approve/deny and deferred-consent paths in both UIs (Keycloak path gated behind KEYCLOAK_E2E=1).

Spec compliance

Reviewed against aauth-spec/draft-hardt-oauth-aauth-protocol.mdcompliant: auth token structure (aa-auth+jwt, required claims, ≤1h lifetime, nested act), signed PS→AS federation, 202 requirement grammar (RFC 8941), claims push, and auth-token delivery verification. No spec violations found.

Validation

  • Unit/integration: passing.
  • e2e: 25 passed, 1 skipped (Keycloak-gated).
  • make demo and make demo-keycloak exercised end-to-end.

dasiths added 22 commits June 2, 2026 17:55
Add the Phase 1 baseline for four-party (federated) AAuth access: a
pure-SDK Access Server sample plus an isolated WhoAmI federated branch
and a manual PS->AS exercise. No new public SDK API is required.

- samples/MockAccessServer (port 5500): serves /.well-known/aauth-access.json
  + JWKS via MapAAuthAccessServerWellKnown; POST /token verifies the PS
  jwks_uri signature (with trusted-PS host pinning), verifies the body's
  agent_token and resource_token (aud=AS), and mints an aa-auth+jwt with
  dwk=aauth-access.json. Policy is a hard-coded allow stub this phase.
- samples/WhoAmI: isolated /federated verification + challenge branch that
  emits a resource token with aud=AS (ChallengeOptions.PersonServerAudience)
  and trusts the AS issuer for the AS-issued auth token.
- tests: MockAccessServerTests exercises the PS->AS POST /token flow,
  asserting the minted token shape and rejecting wrong-aud resource tokens
  and untrusted PS hosts. Full suite green (350 passed).
- devcontainer: add docker-outside-of-docker for the upcoming Keycloak
  policy-engine phases.
- .agent/plans: research + phased implementation plan for the work.
Add the Person Server's signed PS->AS token request client for the
four-party (federated) flow, plus its request/exception types and tests.

- AccessServerClient (AAuth.Tokens): discovers aauth-access.json, POSTs
  the signed {resource_token, agent_token, upstream_token?} request with
  an https-or-loopback + same-origin SSRF guard, handles 200 and the 202
  deferred poll loop (reusing DeferredPoller), and runs the 7-step Auth
  Token Delivery verification via the existing AuthTokenResponseValidator.
- Recognizes 402 Payment Required as AAuthPaymentRequiredException
  (settlement is out of scope per the spec).
- Surfaces an unhandled 202 requirement=claims as NotSupportedException;
  full identity-claims push is deferred to a dedicated later phase.
- AccessServerRequest groups the payload and delivery-verification context.
- 6 unit tests in AAuth.Tests cover success, upstream passthrough, 402,
  requirement=claims, audience-mismatch delivery failure, and structured
  token-endpoint errors. Full suite: 356 passed.

Plan: Phase 2 marked complete; adds a dedicated requirement=claims phase.
MockPersonServer now branches on the resource token's `aud`: when it names
a trusted Access Server (not the PS itself) the PS forwards a signed PS->AS
request via the SDK AccessServerClient and returns the AS-issued auth token,
rather than minting one itself. When `aud` is the PS, the existing
three-party (collapsed PS+AS) path is unchanged.

- Peek `resource_token.aud` to route; gate federation on
  MockPersonServer:TrustedAccessServers (else 403 untrusted_access_server)
- Verify the resource token's agent binding before forwarding; relay AS
  errors, 402 payment_required, and failed delivery verification as 502
- Register AccessServerClient over a named "aauth-federation" HttpClient so
  the PS->AS transport is testable in-process
- Add MockPersonServerFederationTests (federate, untrusted AS, three-party)
- Document three-party vs four-party in the sample README
Refactor the Mock AS allow-stub into a pluggable IAccessPolicy / AccessDecision
seam so the AAuth crypto stays in the adapter while the allow/deny/needs-
interaction decision is delegated. Selection is config-driven via
AccessServer:PolicyProvider (default `stub`), mirroring the
MockPersonServer:RequireConsent pattern; CI stays pure-.NET.

- IAccessPolicy + AccessDecision (Allow/Deny/NeedsInteraction) + request DTO
- StubAccessPolicy encodes the demo rule locally: any verified agent gets the
  base scope; an elevated scope (whoami:admin) needs the whoami-admin role
- /token evaluates the policy and maps Deny->403 access_denied, an unreachable
  backend->503 (fail closed); NeedsInteraction placeholder pending Step B
- Tests: grant elevated scope for admin agent; deny for non-admin (403)

Part of Phase 4 (Keycloak as the policy engine); Keycloak provider next.
Wire the AS's Keycloak Policy Decision Point behind the IAccessPolicy seam
(Phase 4 Step B). The keycloak provider binds an interactive policy that
runs the OIDC authorization-code login plus a uma-ticket
response_mode=decision verdict, pushing PS-asserted claims via claim_token.

- KeycloakOptions: realm authority, client credentials, resource name
- KeycloakAccessPolicy: IAccessPolicy + IInteractiveAccessPolicy
  (EvaluateAsync defers; BuildAuthorizationUrl + CompleteAsync drive login)
- AccessPendingStore: parks mint inputs across the browser round-trip
- /token emits 202 requirement=interaction (Location=/pending/{id})
- /interaction/login redirect, /interaction/callback completion,
  /pending/{id} poll mirroring the PS deferred shape for DeferredPoller
- Browser-facing /interaction paths excluded from AAuth verification
- Fail closed when Keycloak is unreachable (503 policy_unavailable)

Tests: stub Keycloak HttpMessageHandler keeps CI pure-.NET; 4 new tests
cover the interactive grant, admin scope grant/deny, and the 202 emission.
…-party flow

The Person Server now federates to the Access Server in the background while
relaying the AS's interaction requirement (Keycloak login URL) back to the
agent. A FederatedPendingStore parks the in-flight federation; the agent polls
/federated-pending/{id} until the AS resolves the interactive login.
Add a Keycloak realm import, MockAccessServer keycloak settings, and Makefile
targets (keycloak, access-server, demo-federated, agent-federated) so the
four-party federated flow can be run manually end to end with Keycloak as the
interactive policy engine.
The four-party Keycloak demo broke under docker-outside-of-docker: bind-mount
paths resolved on the host (empty realm import dir -> aauth realm 404) and the
published port was only reachable via the bridge gateway, not localhost.

- Use the docker-in-docker feature (privileged) so bind mounts use
  dev-container paths and localhost:8080 works for both the AS backend and the
  forwarded host browser.
- Forward the demo ports (8080 + 5000/5100/5301/5500).
- Install make in the image (base dotnet/sdk image lacks it).
- demo-federated builds the solution once, then runs services with --no-build
  to avoid a concurrent MSBuild file-lock race.
The MockAgentProvider keeps its agent registry in memory, so the AgentConsole's
on-disk enrollment cache goes stale whenever the AP restarts, causing a 400
invalid_grant on token refresh. agent-federated now clears the cache first, and
a standalone agent-reset target is available.
…arrows

Add the federated (four-party) flow to the GuidedTour with a dedicated
Access Server swimlane and rework the sequence-diagram arrow rendering.

- Add Actor.AccessServer plus SubStep/SubStepsLabel to StepRecord so a
  step can render component-internal hops in a labeled box.
- Add TourMode.Federated and StepFederated* steps (resource token
  aud=AS, PS->AS federation rendered as inner sub-steps, AS-minted
  aa-auth+jwt, replay, inspect); model parse-challenge as Agent->Agent
  self-steps to avoid duplicate right-to-left arrows.
- Rewrite SequenceDiagram into request (solid) -> sub-steps box ->
  response (dotted, after the box) rows; response direction reverses the
  request; box title driven by SubStepsLabel.
- Give the Access Server its own red swimlane lane/highlight; full-height
  swimlanes; dotted response arrows and labels with centered arrowheads.
- Makefile: demo-federated now boots the Orchestrator and runs the PS
  with RequireConsent=true so deferred consent works under the federated
  target (parity with demo).
- Update plan/research: Phase 6 delivered + findings; Phase 7 decisions.
Add MapAAuthAccessServer host helper, IAccessPolicy/IAccessPendingStore
seams, and the AAuthClaimsResponse pairwise-subject + claims push model
read from the request body. Pin trusted PS on pending GET/POST and bind
the origin host; reject untrusted claims pushes.
Add SampleApp Federated page (nav entry + home card + shield-lock icon),
GuidedTour Access Server entity highlighting, and the MockAccessServer
host-helper migration onto the SDK endpoints. Wire the MockPersonServer
claims push and add federated E2E specs.
Replace the demo-federated* targets with stub-default demo-tour /
demo-sampleapp and explicit -keycloak variants, factor the duplicated
Keycloak boot/env into reusable defines, and group targets into labelled
sections. Update docs and plans to match the new target names.
Add a RequireConsent mode to the stub AccessPolicy so the federated flow
returns 202 + an Access-Server-badged consent screen (Approve/Deny
endpoints flip the pending entry), matching Keycloak's interactive UX.
Force the Keycloak path to render its own consent screen via prompt=consent
plus consentRequired on the realm client, and rename the client to
"AAuth Access Server" so the consent prompt reads naturally. Badge the
MockPersonServer consent pages as "Person Server" to disambiguate the two.
…consent

Make the GuidedTour federated narrative and consent button text neutral
between the stub AS and Keycloak (same 202 -> interaction URL -> poll ->
mint; only the destination differs), add the call-chain two-hop deferred
consent walkthrough, and update the SampleApp Federated page copy to
reflect the AS-rendered consent screen.
…pecs

Rewrite the federated specs to click Approve/Deny on the AS consent
screen (popup badge assertion), add the call-chain two-hop deferred
approval spec, bump the picker count to six flows, and run the stub AS
with RequireConsent=true in the Playwright webServer.
Replace the per-app demo-tour/demo-sampleapp (and -keycloak) targets with
a single demo (stub AS, both UIs) and demo-keycloak (live Keycloak, both
UIs), each printing a grouped URL summary at the end. Update the README
and workflow docs to match.
…parity

- Fix PS-side example to call AccessServerClient.FederateAsync (was a
  non-existent ExchangeAsync) with the required request members.
- Note that both the stub and Keycloak AS policies drive interactive
  consent, not Keycloak alone.
- Document the stub AS RequireConsent screen in the Mock Access Server README.
- Mark Phase 7-10 DoD items complete in the implementation plan.
- Add an Access Modes table (aligned with explorer.aauth.dev/access/compare)
  with a demo column to the repo README, and restructure it for progressive
  exposure: See It Run -> Quick Start (client) -> Building Servers.
- Fix the three-party snippets to build their own self-issuing client with
  WithChallengeHandling instead of reusing the HWK client.
- Add a dedicated package README (src/AAuth/README.md) with only absolute URLs
  so nuget.org renders correctly; point AAuth.csproj at it.
- Getting Started: add a demos pointer, link/​demo column for all four modes,
  a four-party section, de-duplicate the self-issuing block, and expand
  Next Steps.
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

Adds four-party federated access (Agent → Resource → Person Server → Access Server) across the SDK, samples, docs, and tests, including a pure-.NET stub Access Server and a Keycloak-backed policy option. This extends the existing three-party PS-asserted model by letting the resource delegate policy to an AS when the resource token’s aud points at an Access Server.

Changes:

  • Introduces Access Server federation primitives in the SDK (request/response types, policy/pending-store abstractions, token builder extensions, and deferred polling hooks).
  • Adds a new MockAccessServer sample (stub + Keycloak policy) and wires a new /federated branch into the WhoAmI resource plus new SampleApp/GuidedTour UI paths and Playwright specs.
  • Expands developer workflow support (Makefile demo targets, devcontainer Docker-in-Docker, documentation updates).
Show a summary per file
File Description
tests/e2e/playwright.config.ts Boots MockAccessServer for e2e runs and documents Keycloak gating.
tests/e2e/helpers/tour.ts Adds Federated tour mode and step-count expectations.
tests/e2e/helpers/consent.ts Adds Keycloak login helper for federated interaction popups.
tests/e2e/helpers/agents.ts Adds Access Server base URL constant for e2e assertions.
tests/AAuth.Tests/Integration/MockPersonServerFederationTests.cs Integration coverage for PS→AS federation behavior and relayed interaction.
tests/AAuth.Tests/Integration/MockAccessServerTests.cs Integration coverage for stub Access Server token endpoint + claims push.
tests/AAuth.Tests/Integration/MockAccessServerKeycloakTests.cs Integration coverage for Keycloak policy adapter (stubbed Keycloak in CI).
tests/AAuth.Tests/AccessServerClientTests.cs Unit tests for AccessServerClient federation behaviors and requirement handling.
tests/AAuth.Tests/AAuth.Tests.csproj Adds MockAccessServer project reference to test project.
src/AAuth/Tokens/AuthTokenBuilder.cs Adds tenant claim support and AdditionalClaims merge with collision checks.
src/AAuth/Tokens/AccessServerRequest.cs New request object for AccessServerClient.FederateAsync.
src/AAuth/Server/IAccessPolicy.cs New Access Server policy seam (IAccessPolicy, decisions, interactive extension).
src/AAuth/Server/IAccessPendingStore.cs New pending-decision store abstraction + in-memory implementation.
src/AAuth/README.md Adds SDK README for NuGet packaging and documents access modes.
src/AAuth/Headers/AAuthClaimsResponse.cs Typed claims-push response for §Claims Required callback.
src/AAuth/Headers/AAuthClaimsRequirement.cs Typed projection for requirement=claims using required_claims body.
src/AAuth/Errors/AAuthPaymentRequiredException.cs Typed exception for 402 Payment Required in federation.
src/AAuth/Agent/DeferredPoller.cs Adds StopWhenAccepted hook for mid-poll requirement changes.
src/AAuth/AAuth.csproj Packages src/AAuth/README.md into the NuGet README.
samples/WhoAmI/Program.cs Adds /federated branch that trusts AS-issued auth tokens and challenges with aud=AS.
samples/SampleApp/playwright-tests/federated.spec.ts E2e spec for federated flow via stub AS interactive consent.
samples/SampleApp/playwright-tests/federated-deferred.spec.ts E2e spec for federated flow via Keycloak (gated by KEYCLOAK_E2E).
samples/SampleApp/Components/Pages/Home.razor Adds Federated card; updates prerequisites command text.
samples/SampleApp/Components/Pages/Federated.razor New Federated demo page showing four-party interaction + polling UX.
samples/SampleApp/Components/Layout/NavMenu.razor.css Adds nav icon for federated page.
samples/SampleApp/Components/Layout/NavMenu.razor Adds nav link for federated page.
samples/SampleApp/appsettings.json Adds AccessServer URL configuration.
samples/README.md Updates sample inventory and adds federated/Keycloak quickstart notes.
samples/MockPersonServer/README.md Documents three-party vs four-party branching by resource_token.aud.
samples/MockPersonServer/FederatedPendingStore.cs New store for PS-side federated pending state while AS resolves.
samples/MockAccessServer/README.md New documentation for MockAccessServer behavior and configuration.
samples/MockAccessServer/Properties/launchSettings.json Launch settings for MockAccessServer sample.
samples/MockAccessServer/Program.cs New Access Server sample host wiring (MapAAuthAccessServer) + interaction endpoints.
samples/MockAccessServer/Policy/StubAccessPolicy.cs Stub policy engine (claims requirement, role gate, optional consent).
samples/MockAccessServer/Policy/KeycloakOptions.cs Keycloak adapter configuration options.
samples/MockAccessServer/Policy/KeycloakAccessPolicy.cs Keycloak-backed interactive policy adapter with UMA need_info support.
samples/MockAccessServer/MockAccessServer.csproj New ASP.NET Core project for the Access Server sample.
samples/MockAccessServer/keycloak/realm-aauth.json Keycloak realm import for demo users/scopes/policies.
samples/MockAccessServer/appsettings.json Default AS config (stub policy, Keycloak defaults).
samples/GuidedTour/wwwroot/app.css Adds Access Server styling and response-arrow rendering improvements.
samples/GuidedTour/TourOptions.cs Adds federated mode + AccessServerUrl option.
samples/GuidedTour/StepRecord.cs Adds AccessServer actor, sub-step label, and response sub-step support.
samples/GuidedTour/README.md Updates GuidedTour docs to include federated mode.
samples/GuidedTour/playwright-tests/picker.spec.ts Updates picker spec for 6 flows.
samples/GuidedTour/playwright-tests/federated.spec.ts New GuidedTour e2e spec for federated approve/deny (stub AS).
samples/GuidedTour/playwright-tests/call-chain.spec.ts Updates call-chain e2e to reflect interactive approvals and new step count.
samples/GuidedTour/Components/StepList.razor Adds Access Server actor display name.
samples/GuidedTour/Components/SequenceDiagram.razor Adds dashed response arrows and sub-step labeling.
samples/GuidedTour/Components/Pages/Tour.razor Adds Federated mode to UI and descriptive text updates.
samples/GuidedTour/Components/EntityHighlighter.cs Adds Access Server highlighting rule.
samples/GuidedTour/appsettings.json Adds default AccessServerUrl.
README.md Expands top-level docs: access modes, demo commands, and updated quickstart structure.
Makefile Adds MockAccessServer, Keycloak demo, demo consolidation, and agent helper targets.
docs/workflows/federated-access.md Adds PS-side and AS-side code guidance + interaction/claims sections.
docs/workflows/call-chaining.md Updates quickstart to use make demo.
docs/getting-started.md Adds interactive “Try the flows” section and federated flow references.
AAuth.slnx Adds MockAccessServer project to solution.
.devcontainer/Dockerfile Installs make for running demo targets in container.
.devcontainer/devcontainer.json Enables Docker-in-Docker and forwards Keycloak/AS ports.
.devcontainer/devcontainer-lock.json Locks Docker-in-Docker feature version.
.agent/plans/2026-06-02-four-party-keycloak-as/phase-12-api-design.md Adds delivered API design record for four-party work.

Copilot's findings

  • Files reviewed: 67/67 changed files
  • Comments generated: 7

Comment thread samples/SampleApp/Components/Pages/Home.razor Outdated
Comment thread samples/README.md Outdated
Comment thread samples/README.md Outdated
Comment thread samples/README.md
Comment thread samples/GuidedTour/Components/Pages/Tour.razor Outdated
Comment thread samples/MockAccessServer/Program.cs Outdated
Comment thread src/AAuth/Server/IAccessPendingStore.cs
dasiths added 2 commits June 3, 2026 11:39
- src(access-server): evict in-memory pending entries after a 10-minute
  TTL so the demo IAccessPendingStore does not grow without bound; the
  TTL outlives the interactive round-trip and poll retries so a minted
  entry is not yanked from under a re-polling Person Server
- samples(access-server): render the configured issuer authority in the
  consent banner instead of a hardcoded localhost:5500
- samples(sampleapp): correct prerequisites to `make demo` and list the
  stub Access Server + both UI ports
- samples(guidedtour): reword the Federated step to reflect the default
  interactive consent at the Access Server (RequireConsent=true)
- samples(readme): replace nonexistent demo-tour/demo-sampleapp targets
  with `make demo` / `make demo-keycloak` and list all started services
Follow-up to the C1 review fix in 0fa4b99 which changed the Home page prerequisites from `make demo-sampleapp` to `make demo`; the e2e assertion still expected the old text.
@dasiths dasiths merged commit c5ceab1 into main Jun 3, 2026
2 checks passed
@dasiths dasiths deleted the feat/four-party-federated-access branch June 3, 2026 11:57
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