> ## Documentation Index
> Fetch the complete documentation index at: https://docs.afauth.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Attestation

> A §10 mechanism that lets services know which runtime an agent is operating in — and gives them a per-human, per-service pseudonym to key anti-abuse state off.

The protocol accepts **any** well-formed Ed25519 keypair — the agent is self-sovereign, no central registry, no gatekeeper. But "any keypair" also means a service has no way to distinguish:

* A legitimate, well-behaved enterprise agent from
* A throwaway abuse-bot generated five seconds ago.

When a service needs that distinction, AFAuth's **attestation** layer
has the agent present a JWT signed by a third party (an *attestor*)
that vouches for the agent's runtime context — including, for the
default `afauth-trust` attestor, that a verified human is on the hook
via a per-service pseudonym `sub_h` (§10.4).

<Note>
  For new integrations, attestation is **on by default**. The
  [`defineService`](/sdk/typescript/server/overview) factory in
  `@afauthhq/server` sets `unclaimed_mode: "attested_only"` and wires
  `trustAttestor()` automatically. Pass `attestation: "off"` to opt out
  for read-only or paid-only services, or `"optional"` when your free tier
  is cheap enough that moderation + graded limits are enough anti-abuse
  (un-attested signups are allowed; a presented attestation still
  validates). Which mode fits is an economic call — see [When you
  want attestation](#when-you-want-attestation). See [the service
  quickstart](/quickstart-service).
</Note>

Attestation has **two default sides** that must line up. The service turns `attested_only` on (the `defineService` default, above). The agent links to a human once — [`afauth trust link`](/guides/link-to-a-human) — and then mints a fresh per-request attestation: [`afauth signup`](/cli/commands/signup) reads the service's `attested_only` flag and auto-mints. A default agent that has never linked can't sign up to a default service, so linking is part of the standard agent journey, not an advanced step.

## When you want attestation

* **Spam resistance.** A bad actor spinning up 10,000 throwaway keypairs looks like 10,000 customers to your quota table. With attestation, all of one human's agents carry the same `sub_h`, so `defineService` **groups them onto one account** (§10.4.4): one account, many devices — the human's PC and phone agents share an account, like signing into one account from two devices. Bucket your free-tier quota / rate limits / bans on the account (or `sub_h`) and a human's fleet draws from one bucket — no legitimate multi-device user is locked out.
* **Pre-claim billing.** A service that wants stronger assurance that someone billable is behind the agent before granting paid features can require an attestation from a commerce attestor (`stripe-projects`, `fido-agent-payments`, `mastercard-verifiable-intent`).
* **Enterprise compliance.** A service serving B2B customers may want to limit pre-claim signup to agents running in known enterprise platforms — `microsoft-entra-agent-id`, `google-cloud-agent-identity`, `aws-iam-agent`.
* **Rate-limit tiering.** Attested agents get higher rate limits than anonymous ones; key the buckets off `sub_h`.

## When you don't

Services that genuinely don't need a human-on-the-hook signal — read-only public APIs, paid-only services where billing is the implicit anti-abuse signal — should pass `attestation: "off"` to `defineService` (or use `new Server({...})` without an attestor). The protocol doesn't require attestation; only the convenience factory does.

## How it works

The agent presents an attestation JWT in a header:

```
AFAuth-Attestation: <JWT signed by an accepted attestor>
```

Required claims per [§10.2](https://github.com/AFAuthHQ/spec/blob/main/spec/core.md#102-attestation-header):

* `iss` — the attestor identifier (e.g., `"stripe-projects"`).
* `sub` — the requesting agent's account DID.
* `exp` — token expiry (unix seconds), in the future at the time of verification.
* Other claims are attestor-specific.

The service verifies the JWT against the attestor's published verification key (asymmetric attestors) or shared secret (HMAC attestors). The verifier MUST validate `iss` matches an accepted attestor and `sub` matches the request's signing DID.

## Three attestor classes

The TypeScript SDK ships three implementations of `Attestor`:

| Class           | Verifies                           | Use for                                                                                          |
| --------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------ |
| `HmacAttestor`  | HS256 JWTs against a shared secret | First-party service-operator attestors (you mint the token yourself)                             |
| `JwksAttestor`  | Asymmetric JWTs against a JWKS URL | The default trust attestor (`afauth-trust`); platform / commerce attestors (Stripe, Entra, etc.) |
| `MultiAttestor` | Dispatches by `iss` claim          | A real service that accepts multiple attestor sources                                            |

```typescript theme={null}
import {
  HmacAttestor,
  JwksAttestor,
  MultiAttestor,
} from "@afauthhq/server";

const attestor = new MultiAttestor([
  new HmacAttestor({
    iss: "my-service",
    secret: process.env.ATTESTATION_SECRET!,
  }),
  new JwksAttestor({
    iss: "stripe-projects",
    jwksUrl: "https://stripe.example/.well-known/jwks.json",
    algorithms: ["ES256"],
  }),
]);
```

Pass the composite `attestor` to `Server`; the `attested_only` enforcement happens automatically. Full recipe: [`examples/recipes/attestor.ts`](https://github.com/AFAuthHQ/typescript-sdk/blob/main/examples/recipes/attestor.ts).

## `afauth-trust` — the default trust attestor

Platform and commerce attestors (Entra, Stripe, etc.) are operated by third parties outside any single service's control, which would otherwise leave `attested_only` unreachable from a clean v0.1 deployment. To close that gap, [AFAP-0006](https://github.com/AFAuthHQ/spec/blob/main/proposals/0006-afauth-trust-attestor.md) reserves `afauth-trust` as a recognized attestor identifier, operated by afauth.org at [`trust.afauth.org`](https://trust.afauth.org).

A trust attestation says **one** thing: the agent DID in `sub` is bound to a human-controlled account verified by the categorical method in the `verification` claim — one of `"email"`, `"oauth"`, `"payment"`. The token carries **no PII** — the consuming service gets a signal, not an identity. Full model: [the trust attestor](/concepts/trust-attestor).

`@afauthhq/server` ships a one-line `trustAttestor()` factory that pre-pins the AFAP-0006 issuer, JWKS URL, and algorithm. For new integrations, you don't even need to call it — [`defineService`](/sdk/typescript/server/overview) wires it in automatically. If you're using `new Server({...})` directly:

```typescript theme={null}
import { trustAttestor } from "@afauthhq/server";

const attestor = trustAttestor();
```

Then list `afauth-trust` in your discovery document's `billing.accepted_attestors`. Full walkthrough: [Accept `afauth-trust` attestations](/guides/accept-afauth-trust).

## `unclaimed_mode = "attested_only"`

This is the discovery-document signal that turns on enforcement. `defineService` sets it by default; if you're using `new Server({...})` directly, declare it yourself:

```json theme={null}
{
  "billing": {
    "unclaimed_mode": "attested_only",
    "accepted_attestors": ["afauth-trust", "stripe-projects"]
  }
}
```

…the server refuses implicit signup from any agent that doesn't present a valid `AFAuth-Attestation` header. The error response is `401 attestation_required`. The account row is **not** created on rejection — failing closed before any side effects.

On the agent side, the [`afauth signup`](/cli/commands/signup) command reads this same field and auto-mints a trust attestation when present; agents that haven't run `afauth trust link` are guided to do so before retrying.

Other modes (`"free"`, `"denied"`) don't enforce attestation; if an agent presents one anyway, the server still validates it (rejecting invalid tokens with `401 invalid_attestation`) but doesn't require one.

## Lifetime

By default, attestations are presented on a **per-request basis**, and the service MUST NOT cache an attestation beyond its `exp` — it's a gate on the signed request, not a session token.

Cadence is a spectrum, and most services want the middle:

* **Signup-only.** Check attestation once when the account is created, then trust the agent's signature. Cheapest — but an attestor-side revoke **never reaches you**, the [§8.5](https://github.com/AFAuthHQ/spec/blob/main/spec/core.md#85-revocation-coverage-and-its-limits) blind spot.
* **Per-request.** Require a fresh attestation on every request. An attestor revoke takes effect within the token lifetime (≤ 15 min), but the attestor sits on every request's critical path.
* **Attested session ([§10.7](https://github.com/AFAuthHQ/spec/blob/main/spec/core.md#107-attested-sessions-periodic-re-presentation)).** Keep a currently-valid attestation **on file** and challenge with `401 attestation_required` when it lapses; the agent re-presents a fresh one. Revocation takes effect within the freshness window, with no per-request attestor dependency. See [Keep attested access live](/guides/keep-attested-access-live).

## Security note

Services that accept attestations MUST validate the attestor's signature against an authoritative key source. Stale JWKS or expired shared secrets can permit forged attestations. The `JwksAttestor` from `@afauthhq/server` uses `jose`'s `createRemoteJWKSet`, which caches valid keys and honours key rotation. For HMAC attestors, rotate the shared secret on a schedule.

## Further reading

* [The trust attestor](/concepts/trust-attestor) — what `afauth-trust` is, what its JWTs prove, and how `trust.afauth.org` is governed.
* [Revocation](/concepts/revocation) — the two levers (attestor-side vs service-side) and which to pull when.
* [Accept `afauth-trust` attestations](/guides/accept-afauth-trust) — step-by-step service-side configuration.
* [Spec §10](https://github.com/AFAuthHQ/spec/blob/main/spec/core.md#10-optional-agent-attestation) — normative attestation rules.
* [Spec §9](https://github.com/AFAuthHQ/spec/blob/main/spec/core.md#9-billing-declaration) — billing modes including `attested_only`.
* [Spec §12.5](https://github.com/AFAuthHQ/spec/blob/main/spec/core.md#125-attestation-forgery) — threat model for compromised attestor keys.
