Ga naar hoofdinhoud
GIDS

Hoe u ondertekende AI-agentverzoeken verifieert

Een praktische uitleg voor het verifiëren van RFC 9421-handtekeningen van geautomatiseerde clients, wat een geldige handtekening bewijst en wat uw logs moeten vastleggen.

Toshendra Sharma

Door Toshendra SharmaOprichter & CEO, AgentCredia · Updated November 13, 2026

Geavanceerd · 11 min lezen

Wat bewijst een ondertekend verzoek?

De meeste geautomatiseerde traffic identificeert zich met een user agent string. Die string is een bewering, en kopiëren kost niets. Ondertekende verzoeken veranderen dat beeld. Wanneer een geautomatiseerde client zijn HTTP-verzoeken ondertekent met een private key en de bijbehorende public key publiceert op een locatie die aan de operator is gekoppeld, kan uw server controleren dat het verzoek echt is geproduceerd door iemand die die key bezit.

De standaard hiervoor is HTTP Message Signatures, gepubliceerd als RFC 9421. De IETF Web Bot Auth working group bouwt hierop voort met conventies voor geautomatiseerde clients: hoe een agent naar zijn key directory verwijst en welke request components het moet ondertekenen. Per november 2026 is het protocoldocument van de werkgroep draft-ietf-webbotauth-httpsig-protocol, dat de Signature-Agent header definieert, een key directory-format op basis van JSON Web Key Sets en een well-known URI om die te serveren. Beide zijn het waard om goed te begrijpen, omdat verificatie subtiel fout kan gaan.

Wees vóór de technische details precies over de uitkomst. Een geldige handtekening laat zien dat het verzoek is ondertekend met een key die is gekoppeld aan een gepubliceerde identifier, zoals een domein van een operator, en dat de ondertekende delen van het verzoek niet zijn gewijzigd. Het laat niet zien wie de agent heeft aangestuurd, of een persoon bevoegdheid heeft gedelegeerd, of de handeling veilig is. Bewijs van identiteit en autorisatie zijn afzonderlijke vragen, en uw applicatie beantwoordt de tweede. Onze blogpost over wat ondertekende AI-agentverzoeken bewijzen werkt die grens uit met voorbeelden.

De betrokken headers

Een ondertekend verzoek bevat meestal drie headers:

  • Signature-Input (RFC 9421, Section 4.1) beschrijft elke signature: een label, de lijst van gedekte componenten en parameters zoals created, expires, keyid, alg, nonce en tag.
  • Signature (RFC 9421, Section 4.2) bevat de signature-bytes voor elk label, base64-gecodeerd, als een structured field.
  • Signature-Agent (uit de Web Bot Auth-conventies) verwijst naar waar de public keys van de client zijn gepubliceerd, meestal een HTTPS URL op het domein van de operator.

Een vereenvoudigd voorbeeld:

HTTP
GET /api/catalog?page=2 HTTP/1.1
Host: shop.example
Signature-Agent: "https://agent.example"
Signature-Input: sig1=("@method" "@authority" "@path" "signature-agent");created=1762934400;expires=1762934700;keyid="kq1";alg="ed25519";tag="web-bot-auth";nonce="d9f1c2"
Signature: sig1=:Base64SignatureBytes==:

Het voorbeeld is vereenvoudigd voor de leesbaarheid. In de huidige working group-draft is Signature-Agent zelf een structured field met signature label als sleutel (bijvoorbeeld sig1="https://agent.example"), dus controleer welke vorm uw verification library verwacht.

Stap 1: Parseer de structured fields correct

Signature-Input en Signature zijn HTTP Structured Fields (RFC 9651, die RFC 8941 vervangt). Parseer ze niet door strings te splitsen. Gequote strings, parameters en inner lists hebben een precieze grammatica, en een slordige parser veroorzaakt zowel onterechte afwijzingen als echte beveiligingsgaten.

Gebruik een onderhouden structured-field-parser, of beter nog, een onderhouden RFC 9421-library die parsing en verification samen afhandelt. De specificatie vanaf nul implementeren is een prima leeroefening en een slechte keuze voor productie. De stappen hieronder beschrijven wat zo'n library doet, zodat u die correct kunt configureren en de uitvoer kunt beoordelen.

Stap 2: Kies welke signature u evalueert

Een verzoek kan meer dan één signature label bevatten. Selecteer de label die past bij uw beleid, bijvoorbeeld de label waarvan tag web-bot-auth is. Weiger het verzoek voor verificatiedoeleinden, niet voor service-doeleinden, als:

  • geen enkele signature de parameters heeft die u vereist,
  • het algorithm niet op uw allowlist staat (bijvoorbeeld ed25519), of
  • de gedekte componenten niet de minimale set bevatten die u vereist.

Een verstandig minimum voor geautomatiseerde clients is @method, @authority en @path, plus signature-agent wanneer die header aanwezig is. Zonder @authority kan een voor één site vastgelegde signature tegen een andere site worden hergebruikt. Zonder @path kan die over endpoints heen worden hergebruikt. De Web Bot Auth protocol draft vereist dat agents @authority of @target-uri afdekken, en waarschuwt dat een signature die alleen de authority afdekt, tot het verlopen is tegen elke method of path op die host verifieert.

Stap 3: Controleer tijdsparameters

Lees created en expires, de tijdsparameters gedefinieerd in RFC 9421, Section 2.3. Doe daarna het volgende:

  1. Weiger signatures waarvan de tijd van created in de toekomst ligt voorbij een kleine klokafwijking.
  2. Weiger signatures die expires hebben gepasseerd.
  3. Als expires ontbreekt, hanteer dan uw eigen maximale leeftijd op basis van created.

Korte geldigheidsvensters beperken hoe lang een vastgelegd verzoek kan worden hergebruikt.

Stap 4: Vind de key

De Signature-Agent header wijst naar de key directory van de operator. Haal die op via HTTPS, en alleen via HTTPS. De Web Bot Auth protocol draft beschrijft de directory als een JSON Web Key Set (keys in JWK-format) die wordt aangeboden vanaf de http-message-signatures-directory well-known URI op die origin. Zoek daarna de key waarvan de identifier overeenkomt met de parameter keyid.

TS
type Jwk = { kid: string; kty: string; crv?: string; x?: string };

const keyCache = new Map<string, { keys: Jwk[]; fetchedAt: number }>();
const KEY_TTL_MS = 60 * 60 * 1000;

export async function getKey(directoryUrl: string, keyId: string): Promise<Jwk | null> {
  const url = new URL(directoryUrl);
  if (url.protocol !== "https:") return null;

  const cached = keyCache.get(url.origin);
  const fresh = cached && Date.now() - cached.fetchedAt < KEY_TTL_MS;
  const keys = fresh ? cached.keys : await fetchKeys(url.origin);
  return keys.find((k) => k.kid === keyId) ?? null;
}

async function fetchKeys(origin: string): Promise<Jwk[]> {
  const res = await fetch(`${origin}/.well-known/http-message-signatures-directory`, {
    headers: { accept: "application/http-message-signatures-directory+json, application/json" },
    signal: AbortSignal.timeout(2000),
  });
  if (!res.ok) return [];
  const body = (await res.json()) as { keys?: Jwk[] };
  const keys = Array.isArray(body.keys) ? body.keys : [];
  keyCache.set(origin, { keys, fetchedAt: Date.now() });
  return keys;
}

Praktische regels voor key discovery:

  • Cache keys met een begrensde levensduur, zodat verificatie niet bij elk verzoek een netwerkcall toevoegt.
  • Stel een korte timeout in en behandel een mislukte fetch als "could not verify", nooit als "verified".
  • Beperk vanaf welke origins u ophaalt als dat kan, of blokkeer ten minste private address ranges om server-side request forgery te voorkomen. De protocol draft noemt server-side request forgery expliciet in de security considerations, precies om deze reden.
  • Koppel identiteit aan de origin, niet aan de vrije tekstnaam in de user agent. De identifier die u vastlegt is de directory origin.

Stap 5: Reconstrueer de signature base

De ondertekenaar heeft niet het ruwe verzoek ondertekend. Hij heeft een canonieke string ondertekend die de signature base heet (RFC 9421, Section 2.5), opgebouwd uit de gedekte componenten in volgorde, gevolgd door de regel @signature-params. Uw verifier reconstrueert dezelfde string uit het verzoek dat hij heeft ontvangen.

Voor het voorbeeld hierboven ziet de base er als volgt uit:

TEXT
"@method": GET
"@authority": shop.example
"@path": /api/catalog
"signature-agent": "https://agent.example"
"@signature-params": ("@method" "@authority" "@path" "signature-agent");created=1762934400;expires=1762934700;keyid="kq1";alg="ed25519";tag="web-bot-auth";nonce="d9f1c2"

Let op de details die verificatie achter proxies breken:

  • @authority moet de host zijn die de client heeft beoogd. Als een load balancer Host herschrijft, gebruik dan de originele waarde die deze doorstuurt.
  • @path sluit de query string uit. Als de client @query afdekte, reconstrueer die dan exact zoals ontvangen.
  • Headerwaarden worden genormaliseerd zoals gespecificeerd: getrimd, met meerdere waarden samengevoegd. Een goede library doet dit voor u.

Stap 6: Verifieer de signature

Met de base en de public key is verificatie één aanroep, volgens de stappen in RFC 9421, Section 3.2. Voor Ed25519 in Node.js, met de ingebouwde crypto module:

TS
import { createPublicKey, verify } from "node:crypto";

export function verifyEd25519(jwk: Jwk, base: string, signatureB64: string): boolean {
  const key = createPublicKey({ key: jwk as any, format: "jwk" });
  return verify(null, Buffer.from(base, "utf8"), key, Buffer.from(signatureB64, "base64"));
}

Als de controle mislukt, is het verzoek niet geverifieerd. Het kan nog steeds worden geserveerd. Het krijgt alleen niet de "verified" basis.

Stap 7: Voorkom replay

Een geldige signature kan worden vastgelegd en opnieuw worden verzonden tot deze verloopt. RFC 9421, Section 7.2.2 behandelt signature replay, en de Web Bot Auth protocol draft verwijst daarnaar in plaats van eigen nonce-regels toe te voegen. Twee controles sluiten het grootste deel van dat venster:

  1. Korte expiry, afgedwongen in Stap 3.
  2. Nonce-tracking. Wanneer een nonce aanwezig is, sla die op met de key identifier totdat de signature verloopt, en weiger een tweede gebruik.
TS
const seen = new Map<string, number>();

export function acceptNonce(keyId: string, nonce: string, expiresAt: number): boolean {
  const id = `${keyId}:${nonce}`;
  const now = Date.now() / 1000;
  for (const [k, exp] of seen) if (exp < now) seen.delete(k);
  if (seen.has(id)) return false;
  seen.set(id, expiresAt);
  return true;
}

In een deployment met meerdere instanties bewaart u nonces in een gedeelde store zoals Redis met een time to live die gelijk is aan de resterende geldigheidsduur.

Stap 8: Leg het resultaat vast met de basis ervan

Verificatie levert bewijs op, geen oordeel. Leg het vast in een vorm die uw analytics en policies kunnen gebruiken:

TS
const result = {
  actor: "agent",
  basis: "verified",
  operator: "https://agent.example",
  keyId: "kq1",
  coveredComponents: ["@method", "@authority", "@path", "signature-agent"],
  verifiedAt: new Date().toISOString(),
};

Wanneer verificatie mislukt of niet kan worden uitgevoerd, leg dat dan ook vast, met de reden: ontbrekende headers, unsupported algorithm, key niet gevonden, verlopen, opnieuw afgespeeld, of signature mismatch. Deze redenen zijn waardevol. Een piek in "key not found" van één operator betekent vaak dat die keys heeft geroteerd, niet dat die u aanvalt.

Wat moet u loggen van signature verification?

Log de origin van de operator, de key identifier, de gedekte componenten, de tijdsparameters, de uitkomst en de foutreden. Log niet de volledige signature-bytes of hele request bodies. Bewaar de logs lang genoeg om patronen te bestuderen en kort genoeg om uw privacyverplichtingen te respecteren.

Hoe moet een geverifieerde signature uw beleid veranderen?

Zodra verzoeken kunnen worden geverifieerd, kunt u ze op een zinvolle manier anders behandelen. Onze post over app policies ontwerpen voor mensen en AI agents behandelt het bredere beleidsmodel:

  • Sta geverifieerde operators toe op read-only routes met een hogere rate limit dan ongeverifieerde automation.
  • Bied hen gestructureerde responses, zoals JSON of Markdown, in plaats van volledige HTML-pagina's.
  • Houd acties met hoge impact achter uw normale autorisatie. Een geverifieerde signature vertelt u welke client belt, niet dat die een account mag wijzigen.

Begin in observation mode. Meet hoeveel van uw geautomatiseerde traffic ondertekend is, welke operators verschijnen en hoe vaak verificatie mislukt, voordat enig beleid daarvan afhangt.

Waar Proof of Actor past

Signature verification is één bron van bewijs naast meerdere andere. Proof of Actor combineert die met andere signals en houdt de basis zichtbaar bij elk resultaat, zodat een geverifieerde agent nooit wordt verward met een agent die zich alleen heeft gedeclareerd. Uw applicatie beslist wat er daarna gebeurt.

Om dit met onze engineers in een productieapp te bouwen, vraag vroege toegang aan. Voor het volledige beeld van wat het bewijs wel en niet kan laten zien, lees Detection & Limitations.

Bronnen

Bouw het met ons.

Sluit u aan bij het Proof of Actor founding developer program.

Eerst liever even praten? Spreek met de oprichters