afauth-trust attestation support for an AFAuth service. After this, agents that link to a human at trust.afauth.org can present an offline-verifiable JWT on each request, and your service can gate features on that signal — either as a softer “free tier” filter (unclaimed_mode = "free" with attestation-aware quotas) or as a hard requirement (unclaimed_mode = "attested_only").
Accepting
afauth-trust is the default posture for a spam-resistant service: defineService already wires trustAttestor() and sets unclaimed_mode = "attested_only" for you. This guide shows what that default does and how to tune it. See the trust attestor concept for what a trust attestation actually proves, and the broader attestation concept for HMAC + multi-attestor patterns.Before you start
You need:- A working AFAuth service using
@afauthhq/server(or any conformant implementation). If you don’t yet, start at Accept AFAuth on your service. - A
service_diddeclared in your discovery document — trust attestations are bound to this audience.
trust.afauth.org. You consume a public JWKS document; no provisioning is required.
The three steps
Declare afauth-trust in your discovery document
Add
If you want both
afauth-trust to your billing.accepted_attestors list. Choose an unclaimed_mode based on how strict you want the gate to be.unclaimed_mode | Behaviour |
|---|---|
"free" | Pre-claim signup is allowed without attestation. If an agent presents one, the server still validates it (rejecting invalid tokens) and your application code can read it for quota tiering. |
"attested_only" | Pre-claim signup is rejected unless a valid attestation from one of accepted_attestors is present. 401 attestation_required on rejection. No account row is created on failure. |
"denied" | Pre-claim signup is rejected outright; attestations are ignored. |
afauth-trust and another attestor (e.g., an internal HMAC or stripe-projects once you’ve enrolled), list them together — the server tries each accepted issuer in turn.Configure the attestor in your Server
For new integrations, the easiest path is If you’re already on For staging or local dev (e.g. running the trust attestor against a tunnelled endpoint), With raw
defineService, which wires the trust attestor and the attested_only discovery declaration in one call:new Server({...}) or need a custom discovery doc, use the trustAttestor() factory directly (@afauthhq/server 0.2.0+ ships it pre-pinned to the AFAP-0006 issuer, JWKS URL, and EdDSA):trustAttestor({ jwksUrl: "..." }) overrides the JWKS URL while keeping the rest of the AFAP-pinned shape. The explicit form is also available — new JwksAttestor({ iss: "afauth-trust", jwksUrl, algorithms: ["EdDSA"] }) — but rarely needed.The Verifier automatically:- Reads
AFAuth-Attestationfrom the request headers. - Verifies the JWT’s signature against the JWKS (with caching + rotation handled by
jose’screateRemoteJWKSet). - Validates
iss == "afauth-trust",aud == your service_did,sub == signing DID, andexpin the future. - Rejects with
401 invalid_attestation(malformed / bad signature / wrongaud/ expired) or401 attestation_required(missing underattested_only).
MultiAttestor — e.g. afauth-trust plus an internal HMAC, or a self-hosted attestor under its own iss. With defineService, the attestor’s issuers are advertised in billing.accepted_attestors automatically — no need to hand-list them:new Server({...}) (no defineService) you write the full discovery document yourself, so list every iss in billing.accepted_attestors to match the attestors you wired — it isn’t derived for you there.Use the verification claim in your application
When the verifier accepts an attestation, the The verification rank is your policy. The protocol takes no opinion on whether
verification claim is available to your application code. Use it however your policy needs — gate features, tier rate limits, log it.payment > oauth > email or any other ordering — each service decides what each signal unlocks. The trust attestor always emits the strongest method the linked human has on file.Test your integration end to end
Once configured, you can validate the full path locally with the reference CLI from a fresh agent identity:AFAuth-Attestation header and your service will accept it. This is the default agent journey end to end: an agent built from the agent quickstart links to a human and mints exactly the attestation your attested_only service requires.
Failure modes you might hit
| Code | Cause | Fix |
|---|---|---|
attestation_required | unclaimed_mode = "attested_only" and no AFAuth-Attestation header. | Agent needs to mint one against trust.afauth.org/v1/token and resend. |
invalid_attestation | JWT signature failed, iss ≠ afauth-trust, aud ≠ your service_did, sub ≠ signing DID, or exp is past. | Mint a fresh JWT (≤ 900 s expiry). Check your service_did matches the aud claim byte-for-byte. |
| Verification cache stale | A new kid was just rotated and your JwksAttestor’s cache hasn’t refreshed. | jose’s createRemoteJWKSet refreshes on unknown kid; if you’ve layered an additional cache, drop it. The trust attestor pre-announces new kids 900+ seconds before first use. |
Operational notes
- Offline verification means uptime is bounded by your service. A brief
trust.afauth.orgoutage does not affect verification of already-issued tokens — only the issuance of new ones (and only for the duration of the outage). - Don’t cache attestations beyond their
exp. §10 explicitly forbids it. The 900-second cap is the revocation window. - You do not need to pin
kid. Newkids are published 900+ seconds before first use;JwksAttestorfollows the JWKS, no manual key management on your side.
Where to next
Trust API reference
JWKS, link flow, token endpoint, errors.
The attestation concept
HMAC, MultiAttestor, the full §10 surface.
Spec §10
Normative attestation rules.
List on the registry
Once you accept attestations, get discovered.