Human-in-the-loop approval platform
Out-of-band approvals via CIBA with budgets, timeouts, and a full decision log.
Goal
Build a reusable HITL approval service that any agent can plug into: CIBA + WebAuthn step-up + capability-token issuance + user dashboard. Demonstrate that a fully-compromised agent can still only do user-approved actions.
Stack
- IdP: Keycloak with CIBA enabled (
oauth2.device.authorization.grant.enabled+ CIBA settings). - Approval service: FastAPI + Postgres + Redis.
- WebAuthn:
webauthnPython lib +@simplewebauthn/browserfor the front-end. - Front-end: React or SvelteKit dashboard ("My agents", "Pending approvals", "History").
- Demo agent: existing helpdesk agent calling
refund_order(order_id, amount).
Architecture
Agent → Approval service: POST /approvals { action, args, binding_message }
↓ OPA policy decision (lesson 6.1)
↓ Trigger CIBA at IdP OR push in-app notification
↓ Persist approval record
User app → Approval service: GET /approvals?status=pending
User taps Approve → WebAuthn ceremony → POST /approvals/{id}/approve {assertion}
Approval service → IdP /token (CIBA) → receive token with RAR
→ mint capability token bound to agent's DPoP key
→ return to agent
Agent → payments-api with capability token + DPoP
payments-api enforces cap vs args + writes auditBuild plan (7 days)
- Configure Keycloak CIBA + mobile-push (or simulate with console pushes).
- Build approval-service Postgres schema (
approvals,decisions,webauthn_credentials). - Implement
/approvals+/approvals/{id}/approve|reject+ RAR construction. - WebAuthn registration + assertion flows (passkey).
- Front-end: "Pending approvals" + "Connected agents" + "History" + "Disconnect agent".
- Capability-token minting service (extending lesson 6.1 work).
- Wire demo agent to request a refund; observe the full flow.
- Add expiry + cool-down + dual-approval option.
- Add dashboards: approval volume, rejection rate, time-to-decide, agent ranking.
Acceptance criteria
- Approving via WebAuthn yields a capability token bound to that transaction.
- A captured approval can't be reused (jti rejection).
- Rejecting blocks the agent + logs the decision.
- Expiry kills stale approvals.
- Front-end shows full history per agent; user can revoke.
- Dual approval works: two distinct users required for
delete_tenant.
Stretch
- Push via Twilio Verify + Auth0 Guardian as fallback channels.
- Adaptive risk score driving when step-up is required.
- Slack approvals for ops-style agents.
- "Approval-fatigue" guardrail: warn the user when approvals exceed N/hr.
- Integrate with the audit pipeline from project 1.
Interview talking points
- Why per-action approval beats per-session.
- Phishing-resistance of WebAuthn vs SMS.
- CIBA
binding_messagedesign. - Approval-fatigue and how to combat it.
- Dual approval failure modes (same-person-two-tabs).