Skip to content

guides

Intended Documentation

Verify Decision Tokens

Verify RS256 authority decision tokens locally and with the verification gateway.

Verify Authority Tokens#

Tokens returned in authorityDecisionToken are signed with RS256 and scoped to tenant, adapter, and action claims.

Token Claims (Runtime)#

Typical claim set includes:

  • intentId
  • tenantId
  • adapterId
  • adapterTarget
  • targetSystem
  • proposedAction
  • decision
  • issuedAt
  • expiresAt
  • nonce

Local Verification Flow#

  1. Parse token header and read kid.
  2. Fetch key set from GET /tenants/:tenantId/authority-keys/public.
  3. Select matching key by kid.
  4. Verify JWT signature and required claim constraints.

TypeScript Example#

typescript
import { createPublicKey } from "node:crypto";
import { jwtVerify, decodeProtectedHeader } from "jose";

const token = process.env.AUTHORITY_TOKEN!;
const tenantId = "tenant_acme_prod";

const header = decodeProtectedHeader(token);
if (!header.kid) throw new Error("Missing kid");

const keysResp = await fetch(`https://api.intended.so/tenants/${tenantId}/authority-keys/public`, {
  headers: { Authorization: `Bearer ${process.env.API_KEY}` },
});
const keysJson = await keysResp.json();
const key = keysJson.keys.find((k: any) => k.kid === header.kid);
if (!key) throw new Error("No matching key for kid");

const publicKey = createPublicKey(key.publicKeyPem);
const { payload } = await jwtVerify(token, publicKey, {
  algorithms: ["RS256"],
});

if (payload.tenantId !== tenantId) throw new Error("Tenant mismatch");
if (payload.decision !== "APPROVED") throw new Error("Token is not approved");

Gateway Verification Flow#

Use POST /verify/token when you want centralized verification.

bash
curl -X POST https://api.intended.so/verify/token \
  -H "Authorization: Bearer $API_KEY" \
  -H "x-tenant-id: tenant_acme_prod" \
  -H "Content-Type: application/json" \
  -d '{
    "token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS0xIn0...",
    "publicKeyPem": "-----BEGIN PUBLIC KEY-----...",
    "expectedKid": "key-1",
    "expectedTenantId": "tenant_acme_prod",
    "expectedAdapterId": "github-actions-adapter",
    "maxTokenTtlSeconds": 300,
    "clockSkewSeconds": 30
  }'

Failure Handling#

FailureCauseAction
Invalid signaturetampered token or wrong keydeny and log security event
Expired tokenTTL exceededre-submit /intent to obtain a new token
Tenant mismatchtoken replayed across boundarydeny, investigate actor context
Unknown kidkey rotationrefresh keys and retry

Next Steps#