Skip to content

concepts

Intended Documentation

Authority Token Model

The Authority Token's real structure, Ed25519 signing, per-tenant keys, single-use nonce, 300-second lifetime, and the exact local verification steps and failure reasons.

Authority Token Model#

An Authority Token is the cryptographic proof that a specific action was verified against policy and approved. It is not an identity credential and not an OAuth access token — it is a single-use authorization proof, bound to one intent, that a downstream system verifies locally before it executes. An Authority Token is minted only on an APPROVED decision; DENIED and ESCALATED produce none.

Authority Token lifecycle
A token is minted on APPROVED as an Ed25519 JWT, carried in the connector envelope, verified locally by the adapter, then its nonce is consumed for single use. Replay, expiry, kid mismatch, and revocation are rejection states.

Minted on APPROVED, verified locally, consumed once. Replay, expiry, key-id mismatch, and revocation are all rejected.

Structure#

The Authority Token is an Ed25519 JWT — three base64url sections (header, claims, signature) joined by dots.

json
{ "alg": "EdDSA", "typ": "JWT", "kid": "tenant_acme_prod-1749200000000-9f3a1c2b" }
FieldValue
algEdDSA — Ed25519 (Edwards-curve) signature.
typJWT.
kidThe tenant's signing-key id. Verifiers use it to select the correct public key; pinning it is mandatory.

Claims#

The claims bind the token to exactly one intent, tenant, and execution target:

ClaimMeaning
intentIdThe intent this token authorizes (UUID).
tenantIdTenant scope.
adapterIdThe adapter permitted to consume the token.
adapterTargetThe specific target the adapter will act on.
targetSystemThe system the action targets.
proposedActionThe action that was approved.
decisionAlways APPROVED — only approvals mint tokens.
issuedAt / expiresAtValidity window (see lifetime below).
nonceA ≥32-character random value enforcing single use.

At signing time the runtime also sets the standard JWT registered claims iss: "intended-authority", aud: <adapterId>, and integer iat / exp.

json
{
  "intentId": "8aa3f5f6-b1a9-4c5b-a29f-b489f7d0be58",
  "tenantId": "tenant_acme_prod",
  "adapterId": "github-actions-adapter",
  "adapterTarget": "acme/platform-api",
  "targetSystem": "acme/platform-api",
  "proposedAction": "dispatch release workflow",
  "decision": "APPROVED",
  "issuedAt": "2026-06-06T14:30:00.000Z",
  "expiresAt": "2026-06-06T14:35:00.000Z",
  "nonce": "9f3a1c2b7e4d8a60f1b2c3d4e5f60718",
  "iss": "intended-authority",
  "aud": "github-actions-adapter"
}

Lifetime#

Authority Tokens are short-lived by design: the default and the hard maximum lifetime are both 300 seconds (5 minutes). A token cannot be issued with a longer TTL — the signer rejects it. Combined with single-use semantics, this keeps the window in which a leaked token could be misused minimal.

Signing keys#

Each tenant has its own signing key pair; one tenant's key never signs another tenant's tokens.

  • Algorithm — algorithm-agnostic, Ed25519 by default (RSA‑4096 / ES256 selectable).
  • At rest — private keys are encrypted (AES‑256‑GCM) in the key store and are not exportable.
  • Lifecycle — keys are ACTIVE, PREVIOUS, or RETIRED. Verification accepts ACTIVE and PREVIOUS; RETIRED keys are excluded.
  • Rotation — rotating a key issues new tokens under a new kid while in-flight tokens signed by the now-PREVIOUS key still verify, so rotation causes no downtime.

Verification#

Verification is local: the consumer needs only the tenant's public key for the token's kid, which it can cache and refresh on a schedule. It never calls back to the Intended runtime. The connector SDK's BaseAdapter performs these checks before any action runs; you can also verify directly with @intended/verify.

Select the key by kid

Decode the header, read kid, and load the tenant's public key for it. An unknown or unpinned kid is rejected (TOKEN_KID_MISMATCH).

Verify the Ed25519 signature

A failed signature check is rejected (TOKEN_SIGNATURE_INVALID).

Validate the claims

Assert tenantId, adapterId, and adapterTarget match this execution, decision === "APPROVED", iss/aud match, and the token is unexpired and within the 300s max TTL.

Consume the nonce

The nonce is consumed atomically (unique per tenant). A replay whose nonce is already consumed is rejected. This is what makes the token single-use.

Verifying in code#

ts
import { verifyToken } from "@intended/verify";

const result = await verifyToken({
  token,                         // the authorityDecisionToken from POST /intent
  publicKeyPem,                  // tenant public key for the token's kid
  expectedKid: kid,              // pin the key id (required)
  expectedTenantId: "tenant_acme_prod",
  expectedAdapterId: "github-actions-adapter",
  maxTokenTtlSeconds: 300,
  clockSkewSeconds: 60,
});

if (!result.valid) throw new Error(result.reason); // e.g. TOKEN_EXPIRED
// result.claims is the validated claim set

Failure reasons#

Verification is fail-closed: any failure rejects the action. Common reasons:

ReasonCause
TOKEN_SIGNATURE_INVALIDSignature does not verify against the key
TOKEN_KID_MISMATCHkid not pinned / not a known tenant key
TOKEN_TENANT_MISMATCHToken tenant ≠ expected tenant
TOKEN_ADAPTER_MISMATCHToken adapter/target ≠ this execution
TOKEN_EXPIREDPast expiresAt
TOKEN_TTL_EXCEEDS_LIMITTTL beyond the 300s maximum
TOKEN_CLAIMS_INVALIDClaims fail schema validation

Info

The Ed25519 Authority Token described here is the token returned by POST /intent and verified at execution. Intended also uses an internal HMAC-signed permission token for some policy-pack flows; it is not the artifact downstream services verify, and it is not interchangeable with the Authority Token.

Authority Token Model | Intended