Skip to Content
GuidesIssue & Verify Delegations

Issue & Verify Delegations

This guide shows how an owner grants permissions to an agent, and how a service verifies them.

The flow

Owner Agent Service | | | | 1. Issue delegation | | |------------------------------->| | | | 2. Present delegation | | |----------------------------->| | | | | | 3. Verification result | | |<-----------------------------|

Step 1: Set up owner and agent

import { createAgent, generateKeyPair } from "@credat/sdk"; // Owner's key pair (in production, load from a secure key store) const ownerKeyPair = generateKeyPair("ES256"); const ownerDid = "did:web:acme.com"; // Create the agent const agent = await createAgent({ domain: "acme.com", path: "agents/email-bot", algorithm: "ES256", });

Step 2: Issue a delegation

import { delegate } from "@credat/sdk"; const delegation = await delegate({ agent: agent.did, // Who receives the delegation owner: ownerDid, // Who grants it ownerKeyPair: ownerKeyPair, // Owner signs with their private key scopes: ["email:read", "email:send"], constraints: { allowedDomains: ["gmail.com", "outlook.com"], maxTransactionValue: 0, // No financial operations rateLimit: 100, // Max 100 operations }, validFrom: "2026-01-01T00:00:00Z", validUntil: "2026-12-31T23:59:59Z", }); console.log(delegation.token); // "eyJhbGciOiJFUzI1NiIs..." --- compact SD-JWT VC token console.log(delegation.claims); // { // agent: "did:web:acme.com:agents:email-bot", // owner: "did:web:acme.com", // scopes: ["email:read", "email:send"], // constraints: { allowedDomains: ["gmail.com", "outlook.com"], ... }, // validFrom: "2026-01-01T00:00:00Z", // validUntil: "2026-12-31T23:59:59Z" // }

The delegation.token string is what the agent stores and presents to services. It’s a self-contained, signed credential.

Step 3: Verify a delegation

On the service side, verify with just the owner’s public key:

import { verifyDelegation } from "@credat/sdk"; const result = await verifyDelegation(delegation.token, { ownerPublicKey: ownerKeyPair.publicKey, }); if (result.valid) { console.log("Agent:", result.agent); // "did:web:acme.com:agents:email-bot" console.log("Owner:", result.owner); // "did:web:acme.com" console.log("Scopes:", result.scopes); // ["email:read", "email:send"] console.log("Constraints:", result.constraints); console.log("Valid until:", result.validUntil); } else { console.error("Verification failed:", result.errors); }

Step 4: Check scopes before acting

import { hasScope, hasAllScopes } from "@credat/sdk"; // Single scope check if (hasScope(result, "email:send")) { // OK to send email await sendEmail(payload); } // Multiple scope check if (hasAllScopes(result, ["email:read", "email:send"])) { // Full email access granted }

Step 5: Enforce constraints

if (result.valid && result.constraints) { const { allowedDomains, maxTransactionValue, rateLimit } = result.constraints; // Check domain if (allowedDomains && !allowedDomains.includes(targetDomain)) { throw new Error(`Agent not allowed to access ${targetDomain}`); } // Check transaction value if (maxTransactionValue !== undefined && amount > maxTransactionValue) { throw new Error(`Transaction ${amount} exceeds limit ${maxTransactionValue}`); } // Check rate limit (you'd track this in your own state) if (rateLimit !== undefined && currentCount >= rateLimit) { throw new Error("Rate limit exceeded"); } }

With revocation

To support revoking delegations after they’ve been issued:

import { delegate, createStatusList, setRevocationStatus, isRevoked } from "@credat/sdk"; // 1. Create a status list (minimum size: 131072) const statusList = createStatusList({ id: "status-list-1", issuer: ownerDid, url: "https://acme.com/.well-known/status-list.json", }); // 2. Issue with a status list entry const delegation = await delegate({ agent: agent.did, owner: ownerDid, ownerKeyPair: ownerKeyPair, scopes: ["files:read"], statusList: { url: "https://acme.com/.well-known/status-list.json", index: 42 }, }); // 3. Later, revoke it setRevocationStatus(statusList, 42, true); // 4. Check revocation const revoked = isRevoked(statusList, 42); console.log(revoked); // true

With expiration

// Short-lived delegation (1 hour) const shortLived = await delegate({ agent: agent.did, owner: ownerDid, ownerKeyPair: ownerKeyPair, scopes: ["api:call"], validUntil: new Date(Date.now() + 60 * 60 * 1000).toISOString(), }); // Verification will fail after expiration // result.valid === false // result.errors[0].code === "DELEGATION_EXPIRED"

Error handling

const result = await verifyDelegation(token, { ownerPublicKey }); if (!result.valid) { for (const error of result.errors) { switch (error.code) { case "DELEGATION_SIGNATURE_INVALID": // Token wasn't signed by this owner break; case "DELEGATION_EXPIRED": // Delegation has expired break; case "DELEGATION_REVOKED": // Owner revoked this delegation break; case "DELEGATION_SCOPE_INVALID": // No scopes provided break; } } }

Next steps

Last updated on