Ekam blog · 30 June 2026
How offline token verification works (and why it's fast)
The fastest authorization call is the one you don't make. Ekam is designed so the thing on your request hot path — checking a token — never leaves your process. This post is the architecture, the exact verification steps, and honest, reproducible numbers.
The shape of a token
Ekam issues ES256 JWTs (asymmetric P-256). The signing key lives only in Ekam; everyone else verifies with the public half, published at a JWKS endpoint. A decoded agent token carries:
{
"iss": "https://ekam.olakrutrim.com",
"sub": "agt_...", // the agent (a first-class identity)
"type": "agent", // or "human"
"aud": "https://your-gateway.example", // RFC 8707 - valid for ONE resource
"scope": "models:invoke", // attenuated by the blueprint
"act": { "sub": "own_...", // delegation chain: agent -> owner -> human
"act": { "sub": "hum_..." } },
"tenant": "...", "exp": 1750000000
}
Tokens are short-lived (default 15 min, capped at 1 hour) and compact — about 640 bytes in practice — so they fit in a header without bloating every request.
What your gateway actually does
On the hot path your gateway runs five local checks, none of which call Ekam:
- Fetch the JWKS once from
/.well-known/jwks.json(kidekam-2026-06) and cache it. Keys rotate bykid, so the cache is safe to hold. - Verify the signature (ES256) against the cached public key.
- Check
issequals Ekam, andaudequals your resource (RFC 8707 — a token minted for gateway A is useless at gateway B). - Check
exp(and reject the few sensitive scopes you don't allow). - Map the principal to a budget — read
sub+ theactowner, attribute usage, meter, and (when you charge) bill the owner.
That's it for the common case. The SDK is one import:
import { createEkamVerifier } from "@krutrim/ekam-verify";
const verify = createEkamVerifier({
issuer: "https://ekam.olakrutrim.com",
jwksUri: "https://ekam.olakrutrim.com/.well-known/jwks.json",
audience: "https://your-gateway.example",
});
const principal = await verify(token); // { agentId, ownerId, scopes, tenant, act, ... }When you need live revocation
Offline verification has one trade-off: a token stays valid until it expires, even if you revoked it 30 seconds ago. Ekam closes that two ways, and you pick per your risk:
- Introspection (pull) —
POST /oauth/introspectper call (or cached for a few seconds) reflects revocation immediately (RFC 7662). Costs a round-trip. - CAEP push (no polling) — register an SSF stream and Ekam pushes a signed
session-revokedSecurity Event Token (RFC 8417) the instant you pull the kill-switch, so your gateway evicts the token from its cache in real time.
Short TTLs + push give you most of the safety of online checks at most of the speed of offline ones.
The numbers (measured, reproducible)
Methodology: a single Node 23 process on a dev laptop, in-process verification after the JWKS cache is warm, averaged over thousands of iterations; HTTP figures are localhost round-trips. These are not a load-tested production benchmark — they're a floor that scales linearly across cores and pods.
| Operation | Result |
|---|---|
| Offline verify (hot path) | ~18,000 / sec / core · mean ~0.055 ms · p50 ~0.05 ms · p99 < 0.1 ms |
| Broker a token (HTTP, off hot path) | p50 ~20 ms · p99 ~23 ms |
| Introspection (HTTP, optional) | p50 ~20 ms |
| Token size | ~640 bytes |
The point isn't the absolute figures — it's the shape: brokering happens once per task; verifying happens on every request and is effectively free. Security is added off the latency budget that matters.
Verifier docs Cookbook verify-gateway.ts