@agentlair/verify
v0.1.0
Published
Lightweight AAT (Agent Authentication Token) verification for Node.js, Bun, and edge runtimes. Fetches JWKS from agentlair.dev, caches keys, and validates EdDSA JWTs in one call.
Maintainers
Readme
@agentlair/verify
Lightweight AAT (Agent Authentication Token) verification for Node.js, Bun, Deno, and edge runtimes.
Fetches JWKS from agentlair.dev, caches keys, and validates EdDSA JWTs in one function call. Zero configuration needed.
Install
npm install @agentlair/verify
# or
bun add @agentlair/verifyQuick Start
import { verifyAAT } from '@agentlair/verify';
const result = await verifyAAT(token);
if (result.valid) {
console.log('Agent ID:', result.agentId); // "acc_abc123"
console.log('Agent email:', result.operatorEmail); // "[email protected]"
console.log('Scopes:', result.scopes); // ["mcp:tools:read", ...]
console.log('Issued at:', result.issuedAt); // Date
console.log('Expires at:', result.expiresAt); // Date
} else {
console.error('Token rejected:', result.error);
}API
verifyAAT(token, options?)
Verifies an AgentLair AAT. Fetches JWKS from agentlair.dev, caches keys, and validates the EdDSA signature and JWT claims.
const result = await verifyAAT(token, {
jwksUrl: 'https://agentlair.dev/.well-known/jwks.json', // default
audience: 'https://my-service.example.com', // optional: enforce aud claim
maxAge: '1h', // optional: reject tokens older than 1 hour
cacheTtl: 300_000, // JWKS cache TTL in ms (default: 5 min)
requiredClaims: { iss: 'https://agentlair.dev' }, // optional: additional claim checks
});Returns: VerifyResult
// On success:
{
valid: true,
agentId: string, // sub claim — AgentLair account ID
operatorEmail: string | undefined, // al_email claim
issuedAt: Date,
expiresAt: Date,
scopes: string[], // al_scopes claim
claims: AATClaims, // full decoded payload for advanced use
}
// On failure:
{
valid: false,
error: string, // human-readable reason
}clearJWKSCache()
Clears the module-level JWKS cache. Useful in tests or when forcing a key refresh.
import { clearJWKSCache } from '@agentlair/verify';
clearJWKSCache();Middleware
Express
import express from 'express';
import { createExpressMiddleware } from '@agentlair/verify';
const app = express();
// Protect all /api routes
app.use('/api', createExpressMiddleware({
audience: 'https://my-api.example.com',
}));
app.get('/api/data', (req, res) => {
// req.aat is set after successful verification
console.log('Agent:', req.aat?.agentId);
console.log('Scopes:', req.aat?.scopes);
res.json({ ok: true });
});TypeScript: extend the request type to get autocomplete:
declare global {
namespace Express {
interface Request {
aat?: import('@agentlair/verify').VerifyResult & { valid: true };
}
}
}Hono
import { Hono } from 'hono';
import { createHonoMiddleware } from '@agentlair/verify';
const app = new Hono<{
Variables: { aat: import('@agentlair/verify').VerifyResult & { valid: true } }
}>();
app.use('/api/*', createHonoMiddleware({
audience: 'https://my-api.example.com',
}));
app.get('/api/data', (c) => {
const aat = c.get('aat');
return c.json({ agentId: aat.agentId, scopes: aat.scopes });
});Fastify
import Fastify from 'fastify';
import { createFastifyHook } from '@agentlair/verify';
const fastify = Fastify();
fastify.addHook('preHandler', createFastifyHook({
audience: 'https://my-api.example.com',
}));
fastify.get('/api/data', async (request) => {
console.log('Agent:', request.aat?.agentId);
return { ok: true };
});How it works
- The JWT header contains a
kid(key ID). @agentlair/verifyfetcheshttps://agentlair.dev/.well-known/jwks.jsonand caches the response for 5 minutes (configurable).- The matching JWK is selected by
kid— key ID matching, not array position. This means key rotation is seamless. - The EdDSA (Ed25519) signature is verified using the public key.
- Standard JWT claims (
exp,iat,iss) and AgentLair-specific claims (al_scopes,al_audit_url) are validated.
Error messages
Clear error messages for common failure modes:
| Situation | Error |
|-----------|-------|
| Token expired | Token expired at 2026-04-20T12:00:00.000Z |
| Wrong issuer | Invalid claim "iss": check failed |
| Audience mismatch | Invalid claim "aud": check failed |
| Bad signature | Signature verification failed: token may be tampered or signed with wrong key |
| Unknown key | No matching key in JWKS: key ID not found or key has been rotated |
| JWKS fetch failed | Failed to fetch JWKS: <network error> |
| Malformed JWT | Malformed token: expected 3-part JWT (header.payload.signature) |
Types
import type {
AATClaims, // Full JWT payload type
VerifyOptions, // Options for verifyAAT()
VerifyResult, // Return type of verifyAAT()
MiddlewareOptions, // Options for middleware factories
} from '@agentlair/verify';AATClaims
interface AATClaims {
// Standard JWT
iss: string; // "https://agentlair.dev"
sub: string; // AgentLair account ID
aud: string; // Target audience URL
exp: number; // Expiration (Unix seconds)
iat: number; // Issued at (Unix seconds)
jti: string; // Unique token ID
// AgentLair-specific
al_scopes: string[]; // Granted scopes
al_audit_url: string; // Audit trail link
al_name?: string; // Agent name
al_email?: string; // Agent email (@agentlair.dev)
// MCP-I Level 2 interop
did?: string; // e.g. "did:web:agentlair.dev:agents:acc_xxx"
// Trust attestation (RFC-001 Phase 1)
al_trust?: {
score: number; // [0, 100]
level: 'intern' | 'junior' | 'senior' | 'principal';
confidence: number; // [0.0, 1.0]
computed_at: string; // ISO 8601
trend: 'improving' | 'stable' | 'declining';
};
}Requirements
- Node.js ≥ 18 (Web Crypto API required)
- Bun (any version)
- Deno (any version)
- Edge runtimes: Cloudflare Workers, Vercel Edge Functions, etc.
License
MIT — AgentLair
