@web42/auth
v0.2.2
Published
Web42 Auth SDK — token introspection client and Express/Hono middleware helpers for the Web42 Network
Downloads
431
Maintainers
Readme
@web42/auth — JavaScript / TypeScript SDK
Web42 Auth client for Node.js, Deno, Bun, and edge runtimes. Validate user tokens, build authenticated A2A agents, and implement AP2 payment mandates.
Requirements
- Node.js 18+ (uses native
fetch) - Or any runtime with the Fetch API (Deno, Bun, Cloudflare Workers, etc.)
Installation
npm install @web42/auth
# or
pnpm add @web42/authFor A2A server support, also install the peer deps:
npm install express @a2a-js/sdkQuick start
import { Web42Client } from "@web42/auth";
const w42 = new Web42Client({
clientId: process.env.WEB42_CLIENT_ID!,
clientSecret: process.env.WEB42_CLIENT_SECRET!,
});
const info = await w42.introspect(bearerToken);
if (info.active) {
console.log(`Authenticated: ${info.email} (${info.provider})`);
} else {
console.log("Token inactive or revoked");
}Reference
new Web42Client(options)
| Option | Type | Description |
|---|---|---|
| clientId | string | Your app's client_id |
| clientSecret | string | Your app's client_secret |
| timeout | number | Request timeout in ms (default 5000) |
.introspect(token: string): Promise<TokenInfo>
Calls POST /introspect. Always resolves — check .active before trusting other fields.
Throws Web42AuthError on unexpected HTTP errors (e.g. 401 for bad credentials).
TokenInfo
interface TokenInfo {
active: boolean;
sub?: string; // user UUID
email?: string;
provider?: string; // "google" | "github"
exp?: number; // Unix timestamp
iat?: number; // Unix timestamp
}Express middleware
import express from "express";
import { Web42Client, createExpressMiddleware } from "@web42/auth";
const w42 = new Web42Client({
clientId: process.env.WEB42_CLIENT_ID!,
clientSecret: process.env.WEB42_CLIENT_SECRET!,
});
const app = express();
app.use(createExpressMiddleware(w42));
app.get("/protected", (req, res) => {
res.json({ user: req.tokenInfo!.sub, email: req.tokenInfo!.email });
});Options
app.use(
createExpressMiddleware(w42, {
// Extract token from a custom header instead of Authorization
getToken: (req) => req.headers["x-user-token"] as string | undefined,
// Custom error handler
onError: (err, req, res, next) => {
res.status(503).json({ error: "Auth unavailable" });
},
}),
);Next.js App Router
// app/api/protected/route.ts
import { NextRequest, NextResponse } from "next/server";
import { Web42Client, checkToken } from "@web42/auth";
const w42 = new Web42Client({ ... });
export async function GET(req: NextRequest) {
const result = await checkToken(w42, req.headers.get("authorization"));
if (!result.ok) {
return NextResponse.json({ error: result.error }, { status: result.status });
}
return NextResponse.json({ user: result.tokenInfo.sub });
}A2A server
createA2AServer wires up a fully authenticated A2A server in one call.
import { Web42Client, createA2AServer } from "@web42/auth";
import type { AgentExecutor, RequestContext, ExecutionEventBus } from "@a2a-js/sdk/server";
const w42 = new Web42Client({
clientId: process.env.WEB42_CLIENT_ID!,
clientSecret: process.env.WEB42_CLIENT_SECRET!,
});
class MyExecutor implements AgentExecutor {
async execute(ctx: RequestContext, bus: ExecutionEventBus) {
const tokenInfo = server.tokenStorage.getStore();
console.log(`Request from: ${tokenInfo?.email}`);
// publish task events to bus...
}
cancelTask = async () => {};
}
const server = createA2AServer({
web42: w42,
card: {
name: "My Agent",
description: "Does something useful",
capabilities: { streaming: true, pushNotifications: false },
skills: [
{
id: "my-skill",
name: "My Skill",
description: "Does something specific",
tags: ["example"],
examples: ["Do the thing"],
},
],
},
executor: new MyExecutor(),
port: 3001,
});
server.listen(() => console.log("Agent ready on port 3001"));AP2 Payments
Build and verify AP2 payment mandates for agent commerce.
import {
requestCartMandateSignature,
verifyPayment,
claimPayment,
buildCartMandateDataPart,
isCartMandatePart,
isPaymentMandatePart,
parseCartMandate,
parsePaymentMandate,
type CartMandate,
type CartMandateContents,
type PaymentMandate,
type PaymentVerification,
type DisplayItem,
type AP2Amount,
} from "@web42/auth";Merchant flow
1. Build and sign a CartMandate
import { requestCartMandateSignature, buildCartMandateDataPart } from "@web42/auth";
const contents: CartMandateContents = {
id: "order_latte_456",
user_signature_required: false,
payment_request: {
method_data: [{ supported_methods: "WEB42_WALLET", data: {} }],
details: {
id: "order_latte_456",
displayItems: [
{ label: "Iced Latte", amount: { currency: "USD", value: 4.50 } },
{ label: "Tax", amount: { currency: "USD", value: 0.45 } },
],
shipping_options: null,
modifiers: null,
total: { label: "Total", amount: { currency: "USD", value: 4.95 } },
},
options: {
requestPayerName: false, requestPayerEmail: false,
requestPayerPhone: false, requestShipping: false, shippingType: null,
},
},
};
// Platform signs the CartMandate with the merchant's key
const cartMandate = await requestCartMandateSignature(client, contents);
// Send to the buyer as an A2A data part
const part = buildCartMandateDataPart(cartMandate);2. Verify payment
After receiving a PaymentMandate from the buyer:
import { verifyPayment } from "@web42/auth";
const result = await verifyPayment(client, paymentMandate);
if (result.valid) {
console.log(`Payment verified: $${result.amount?.value}`);
} else {
console.log(`Invalid: ${result.reason}`);
}3. Claim payment
After fulfilling the order, claim the payment to release escrow:
import { claimPayment } from "@web42/auth";
await claimPayment(client, paymentMandate);
// escrow_status transitions from "held" → "captured"Claim must be called while escrow_status === "held" and before the 72-hour escrow window expires. If unclaimed, the cron job voids the payment and refunds the buyer's wallet.
Buyer / shopping agent flow
Parse a CartMandate from an A2A message
import { isCartMandatePart, parseCartMandate } from "@web42/auth";
for (const part of message.parts) {
if (isCartMandatePart(part)) {
const cart = parseCartMandate(part);
// cart.contents.payment_request.details.total
// cart.merchant_signature (JWS — verifiable against platform JWKS)
}
}Parse a PaymentMandate
import { isPaymentMandatePart, parsePaymentMandate } from "@web42/auth";
for (const part of message.parts) {
if (isPaymentMandatePart(part)) {
const mandate = parsePaymentMandate(part);
// mandate.payment_mandate_contents.payment_mandate_id
// mandate.user_authorization (platform-signed JWT)
}
}Types
| Type | Description |
|---|---|
| AP2Amount | { currency: string; value: number } — ISO 4217 |
| DisplayItem | { label: string; amount: AP2Amount } — line item |
| CartMandateContents | Cart contents before signing |
| CartMandate | Signed cart: { contents, merchant_signature, timestamp } |
| PaymentMandateContents | Payment details: mandate ID, order ID, total, merchant agent |
| PaymentMandate | Payment proof: { payment_mandate_contents, user_authorization } |
| PaymentVerification | Result from verifyPayment: { valid, reason?, ... } |
Agent card helpers
import { buildAgentCard, buildAgentCardSecurity, ap2PaymentExtension } from "@web42/auth";
// Build a complete A2A AgentCard
const agentCard = buildAgentCard({
name: "My Agent",
description: "Does useful things",
url: "https://my-agent.example.com/a2a/jsonrpc",
capabilities: { streaming: true, pushNotifications: false },
skills: [{ id: "skill-1", name: "Skill One", description: "...", tags: [] }],
// Declare AP2 payment support
extensions: [ap2PaymentExtension()],
});Error handling
import { Web42AuthError } from "@web42/auth";
try {
const info = await w42.introspect(token);
} catch (err) {
if (err instanceof Web42AuthError) {
if (err.status === 401) {
// Bad client credentials — check your client_id / client_secret
}
}
}Environment variables
WEB42_CLIENT_ID=550e8400-e29b-41d4-a716-446655440000
WEB42_CLIENT_SECRET=w42_sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxBuilding from source
cd packages/sdk-js
pnpm install
pnpm run build
pnpm run typecheck