The official Node.js SDK for OpenBotAuth signature verification.
Package: @openbotauth/verifier-client
# npm
npm install @openbotauth/verifier-client
# pnpm
pnpm add @openbotauth/verifier-client
# yarn
yarn add @openbotauth/verifier-clientRequirements: Node.js >= 18.0.0
The fastest way to integrate is with the Express middleware:
import express from 'express';
import { openBotAuthMiddleware } from '@openbotauth/verifier-client/express';
const app = express();
// Add middleware (observe mode by default)
app.use(openBotAuthMiddleware());
app.get('/api/content', (req, res) => {
const oba = (req as any).oba;
if (oba.signed && oba.result?.verified) {
// Verified bot - full access
res.json({
content: 'Full article content...',
agent: oba.result.agent
});
} else {
// Anonymous or unverified - limited access
res.json({
content: 'Article preview...',
upgrade: 'Sign requests for full access'
});
}
});
app.listen(3000);For Next.js Server Components and Route Handlers:
// app/api/content/route.ts
import { NextRequest, NextResponse } from 'next/server';
import {
VerifierClient,
buildVerifyRequestForNext,
hasSignatureHeaders
} from '@openbotauth/verifier-client';
const client = new VerifierClient();
export async function GET(request: NextRequest) {
// Check if request has signature headers
if (!hasSignatureHeaders(request.headers)) {
return NextResponse.json({ content: 'Preview only' });
}
// Build verification request from Next.js request
const verifyRequest = buildVerifyRequestForNext(request);
const result = await client.verify(verifyRequest);
if (result.verified) {
return NextResponse.json({
content: 'Full content',
agent: result.agent
});
}
return NextResponse.json(
{ error: 'Verification failed', reason: result.error },
{ status: 401 }
);
}For custom integrations:
import { VerifierClient, VerificationRequest } from '@openbotauth/verifier-client';
const client = new VerifierClient({
verifierUrl: 'https://verifier.openbotauth.org/verify', // default
timeoutMs: 5000 // default
});
const request: VerificationRequest = {
method: 'GET',
url: 'https://example.com/api/content',
headers: {
'host': 'example.com',
'signature-input': 'sig=("@method" "@target-uri" "host");created=1699900000;keyid="key-1";alg="ed25519"',
'signature': 'sig=:base64signature...:',
'signature-agent': 'https://registry.openbotauth.org/jwks/mybot.json'
}
};
const result = await client.verify(request);
if (result.verified) {
console.log('Verified agent:', result.agent?.client_name);
console.log('Key ID:', result.kid);
} else {
console.log('Verification failed:', result.error);
}Main client class for calling the verifier service.
interface VerifierClientOptions {
verifierUrl?: string; // Default: 'https://verifier.openbotauth.org/verify'
timeoutMs?: number; // Default: 5000
}
class VerifierClient {
constructor(options?: VerifierClientOptions);
verify(request: VerificationRequest): Promise<VerificationResult>;
}Request object sent to the verifier:
interface VerificationRequest {
method: string; // HTTP method (GET, POST, etc.)
url: string; // Full request URL
headers: Record<string, string>; // Request headers
body?: string; // Request body (for POST/PUT)
}Response from the verifier:
interface VerificationResult {
verified: boolean; // Whether signature is valid
agent?: { // Agent info (if verified)
client_name: string;
client_uri?: string;
// ... other agent metadata
};
kid?: string; // Key ID used for signing
jwks_url?: string; // JWKS URL for the agent
error?: string; // Error message (if failed)
created?: number; // Signature creation timestamp
expires?: number; // Signature expiration timestamp
}interface MiddlewareOptions {
verifierUrl?: string; // Verifier service URL
mode?: 'observe' | 'require-verified'; // Default: 'observe'
attachProperty?: string; // Request property name (default: 'oba')
timeoutMs?: number; // Verification timeout
}State attached to requests by middleware:
interface OBAState {
signed: boolean; // Request had signature headers
result?: VerificationResult; // Verification result (null if not signed)
}Utility functions for working with RFC 9421 headers:
import {
hasSignatureHeaders,
parseCoveredHeaders,
extractForwardedHeaders
} from '@openbotauth/verifier-client';
// Check if request has signature headers
const hasSig = hasSignatureHeaders(request.headers);
// Parse covered headers from Signature-Input
const covered = parseCoveredHeaders(signatureInput);
// Returns: ['@method', '@target-uri', 'host', ...]
// Extract only safe headers for forwarding to verifier
const safeHeaders = extractForwardedHeaders(
request.headers,
coveredHeaders
);The SDK automatically blocks sensitive headers from being forwarded to the verifier:
cookieauthorizationproxy-authorizationwww-authenticate
If a Signature-Input references any of these headers, the request will be rejected.
All verification requests have a configurable timeout (default 5 seconds). On timeout, verification is treated as failed.
All requests pass through regardless of verification status. Use this for:
- Logging and analytics
- Gradual rollout
- A/B testing between verified and unverified access
app.use(openBotAuthMiddleware({ mode: 'observe' }));Protected paths return 401 for unsigned or failed verification:
app.use(openBotAuthMiddleware({
mode: 'require-verified',
protectedPaths: ['/api/premium', '/api/content']
}));try {
const result = await client.verify(request);
if (!result.verified) {
// Verification failed (invalid signature, expired, etc.)
console.log('Reason:', result.error);
}
} catch (error) {
// Network error, timeout, or verifier service unavailable
console.error('Verification error:', error);
}The package is written in TypeScript and exports all types:
import type {
VerifierClientOptions,
VerificationRequest,
VerificationResult,
MiddlewareOptions,
OBAState
} from '@openbotauth/verifier-client';import express from 'express';
import { openBotAuthMiddleware } from '@openbotauth/verifier-client/express';
const app = express();
app.use(openBotAuthMiddleware({
verifierUrl: 'https://your-verifier.example.com/verify',
mode: 'observe',
timeoutMs: 3000
}));
app.get('/api/data', (req, res) => {
const { signed, result } = (req as any).oba;
res.json({
authenticated: signed && result?.verified,
agent: result?.agent?.client_name || 'anonymous'
});
});// For POST requests that need body in signature
import { buildVerifyRequestForNextWithBody } from '@openbotauth/verifier-client';
export async function POST(request: NextRequest) {
const body = await request.text();
const verifyRequest = buildVerifyRequestForNextWithBody(request, body);
const result = await client.verify(verifyRequest);
// ...
}