Architecture
HumanAuth deployments have three parties plus the humans’ devices, and the names matter because two of them are easy to conflate. If you know OAuth, the roles map exactly:
| Role here | OAuth analogue | Responsibility |
|---|---|---|
| Agent backend | Client | Runs the agent. Calls POST /v1/authorize, hosts the webhook endpoint, holds the receipt, retries with the Idempotency-Key. Orchestration — it asks. |
| HumanAuth platform | Authorization server | Routes the request to registered devices, verifies device co-signatures, evaluates quorum policy, mints the receipt JWS, fires request.decided / request.expired webhooks. |
| Approver device | Resource owner (their signing agent) | A human, plus a hardware-held key. Reviews the plan, confirms with a biometric, co-signs the exact plan-hash-bound bytes. |
| Resource server / gateway | Resource server | The service that executes the action. Verifies the receipt JWS offline with @humanauth/verifier, burns the jti in its replay store, executes exactly once. Enforcement — it acts. |
The load-bearing separation is between the two halves you run: the agent backend orchestrates, the resource server enforces. They are different trust roles even when they are deployed close together.
Topology — who talks to whom
Section titled “Topology — who talks to whom”Read the absences as carefully as the arrows. The resource server is deliberately deaf: it holds no HumanAuth account, subscribes to no webhooks, and accepts no inbound connection from the platform. The only thing it ever fetches from HumanAuth is the public JWKS — cached (default 1 hour), refreshable out-of-band, pinnable for air-gapped deployments. Proof reaches the resource server exactly one way: inside the request that asks it to act.
The full sequence
Section titled “The full sequence”Two clocks run in this picture, and they answer different questions: the request TTL (10 s – 1 h, set at authorize) bounds how long the human has to decide; the receipt expiry (600 s from mint) bounds how long the agent has to present the proof. Details in the Security Model.
The same sequence, animated — signatures land on the receipt, the platform seals it, and the gateway’s lock opens only for a verified receipt:
Three authorization patterns — drag to orbit · open full-screen · failure paths in On-Behalf-Of & Intent
Where the webhook lives
Section titled “Where the webhook lives”In the agent backend — the same trust domain that called authorize. The webhook is a doorbell for the orchestrator: it says the decision landed, so a sleeping agent can wake, fetch the receipt, and proceed. It never carries the proof itself, and it never targets the resource server.
If your tenant’s own backend initiates approvals too (both the agent and your backend can call authorize — the API key is the principal), that caller plays the agent backend role for that request. The role placement never changes: whoever asks, listens; whoever executes, verifies.
The resource server verifies the receipt — and burns the jti
Section titled “The resource server verifies the receipt — and burns the jti”A recurring question: does the gateway verify the receipt or the jti? The receipt — always, all of it.
A bare jti is a random string with no signature on it; anyone can send one, and it proves nothing on its own. Validating a jti by itself would require asking HumanAuth “is this real?” — the online-introspection oracle the whole design deliberately rejects. Instead, the resource server runs the verifier’s full offline chain — platform signature, claims, device co-signatures, action match, plan-hash recomputation — and only after everything passes does it touch the jti: one atomic write into its own replay store to spend it. The receipt is the proof; the jti is its single-use serial number.
That replay store is the one piece of state the resource server owns, and it belongs there: the party that executes must be the party that remembers what it executed. Replicas behind a load balancer share the store (Redis, Postgres, D1 — one claim domain per enforcement boundary); the agent backend keeps no replay state at all.
Can the two halves be one process?
Section titled “Can the two halves be one process?”For small deployments, yes — the agent backend and the resource server can be the same service, even the same handler. The separation is about trust roles, not machines. What must survive any collapse of the deployment diagram: verification runs at the site of execution, on the full JWS, with its own replay claim — never upstream in the orchestrator, and never delegated to a “the agent said it was approved” flag.
See also
Section titled “See also”- Verifier SDK — the enforcement half:
requireReceipt(), replay stores, typed errors - Webhook Subscriptions — the orchestration half’s event feed
- Security Model — the ten checks, offline rationale, threat model
- FAQ — 2FA vs. per-action authorization, consensus limits, receipt ≠ JWT