Agent + On-Behalf-Of + DPoP

An agent that exchanges a user token for a downstream token and binds every call to a private key.

🛠 Intermediate

Goal

Build an end-to-end OBO flow: user signs into a web app, app spawns an agent that calls a resource server "as Alice", with the agent identity preserved via act claim, and every token DPoP-bound. Audit logs prove who did what.

Stack

  • IdP: Keycloak (with Token Exchange + DPoP enabled).
  • App: SvelteKit / Next.js (PKCE flow).
  • Agent runtime: Python (FastAPI + httpx).
  • Resource server: FastAPI tickets-api with JWT + DPoP validation.
  • Audit pipeline: Loki + Grafana.

Architecture

User → App (PKCE)  →  user_access_token (aud: app-api)
       App         →  Token Exchange   →  agent_obo_token (sub=alice, act=agent.helpdesk-v1, aud=tickets-api)
       Agent       →  DPoP-bound calls →  tickets-api  →  Postgres
                       (each request includes a fresh DPoP header)
       All services emit audit records → Loki

Build plan (5 days)

  1. Day 1: Keycloak realm + clients (helpdesk-app, agent.helpdesk-v1) + APIs. Enable Token Exchange + DPoP. mkcert HTTPS.
  2. Day 2: User-facing app with PKCE; show the user a session view. Server-side stores user_access_token in Vault.
  3. Day 3: Agent runtime: on user request, fetch user's token from Vault → Token Exchange → DPoP-bound calls to tickets-api.
  4. Day 4: tickets-api validates: JWT signature, iss, aud, exp, scope, DPoP htu/htm/ath. Emit structured audit logs with sub + act.
  5. Day 5: Loki + Grafana dashboards: events per minute, deny rate, per-agent latency. Write README + threat model.

Acceptance criteria

  • A token captured from the wire can't be replayed (different DPoP key fails).
  • Removing act from the OBO token → audit shows only user, breaking traceability — demonstrate fix.
  • Token Exchange downscoping works: requesting tickets:write when subject token only has tickets:read → IdP denies.
  • Audit log lets you reconstruct any agent action in < 1 minute.

Stretch

  • Replace static client secret with SPIRE workload identity (lesson 2.2).
  • Add CIBA for sensitive delete_ticket action.
  • Add a act chain by spawning a sub-agent; observe nested claims.
  • Add Sigstore-signed Docker images + Kyverno admission.

Interview talking points

  • Why act claim vs impersonation.
  • DPoP value proposition vs static bearer tokens.
  • Why aliases + scopes coexist with claims-driven authz.
  • Tradeoffs: refresh token lifetime vs UX vs theft risk.
  • Audit log schema design.