Fetch / Web Request
When using a framework that exposes Web standard Request and Response (Hono, Next.js Route Handlers, SvelteKit hooks.server.ts, Astro middleware, Remix loaders, Cloudflare Workers, Bun.serve, Deno.serve), the Express-style (req, res, next) middleware signature does not apply. There is no Node req.socket and request.headers is a Headers map rather than a plain object.
The client-certificate-auth/fetch subpath provides a small framework-agnostic adapter for this case.
Usage
import { extractClientCertificateFromRequest } from 'client-certificate-auth/fetch';
const result = extractClientCertificateFromRequest(request, {
certificateSource: 'cloudflare-rfc9440',
});
if (!result.success) {
// Reject with 401 or your framework's equivalent
}The helper returns the same { success, certificate, reason } result as extractClientCertificate() from client-certificate-auth/extractor. Validation helpers from client-certificate-auth/helpers work without modification.
Header-Only
Web Request does not expose the TLS socket, so the Fetch adapter is header-only. Configure a header source via certificateSource or certificateHeader + headerEncoding; fallbackToSocket has no effect.
Runtime requirement
The adapter imports the core extractor, which uses node:crypto (X509Certificate). Runtimes must provide Node-compatible crypto: Node, Bun, Deno, and Cloudflare Workers with nodejs_compat all work. Pure edge runtimes without Node compatibility will fail at import time.
Recipes
Hono
import { Hono } from 'hono';
import { extractClientCertificateFromRequest } from 'client-certificate-auth/fetch';
const app = new Hono();
app.get('/secure', (c) => {
const result = extractClientCertificateFromRequest(c.req.raw, {
certificateSource: 'cloudflare-rfc9440',
});
if (!result.success) return c.text('Unauthorized', 401);
return c.text(`Hello ${result.certificate.subject.CN}`);
});
export default app;Next.js Route Handler
// app/api/secure/route.js
import { extractClientCertificateFromRequest } from 'client-certificate-auth/fetch';
export async function GET(request) {
const result = extractClientCertificateFromRequest(request, {
certificateSource: 'aws-alb',
});
if (!result.success) return new Response('Unauthorized', { status: 401 });
return Response.json({ user: result.certificate.subject.CN });
}SvelteKit hooks.server.ts
import type { Handle } from '@sveltejs/kit';
import { extractClientCertificateFromRequest } from 'client-certificate-auth/fetch';
export const handle: Handle = async ({ event, resolve }) => {
const result = extractClientCertificateFromRequest(event.request, {
certificateSource: 'cloudflare-rfc9440',
});
if (result.success) {
event.locals.user = { cn: result.certificate.subject.CN };
}
return resolve(event);
};Astro middleware
// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';
import { extractClientCertificateFromRequest } from 'client-certificate-auth/fetch';
export const onRequest = defineMiddleware(async (context, next) => {
const result = extractClientCertificateFromRequest(context.request, {
certificateSource: 'aws-alb',
});
if (!result.success) return new Response('Unauthorized', { status: 401 });
context.locals.user = { cn: result.certificate.subject.CN };
return next();
});Cloudflare Workers
import { extractClientCertificateFromRequest } from 'client-certificate-auth/fetch';
export default {
async fetch(request, env) {
const result = extractClientCertificateFromRequest(request, {
certificateSource: 'cloudflare-rfc9440',
});
if (!result.success) return new Response('Unauthorized', { status: 401 });
return new Response(`Hello ${result.certificate.subject.CN}`);
},
};Remix / React Router 7 loader
import type { LoaderFunctionArgs } from 'react-router';
import { extractClientCertificateFromRequest } from 'client-certificate-auth/fetch';
export async function loader({ request }: LoaderFunctionArgs) {
const result = extractClientCertificateFromRequest(request, {
certificateSource: 'aws-alb',
});
if (!result.success) throw new Response('Unauthorized', { status: 401 });
return { user: { cn: result.certificate.subject.CN } };
}Bun.serve
import { extractClientCertificateFromRequest } from 'client-certificate-auth/fetch';
Bun.serve({
port: 3000,
fetch(request) {
const result = extractClientCertificateFromRequest(request, {
certificateSource: 'cloudflare-rfc9440',
});
if (!result.success) return new Response('Unauthorized', { status: 401 });
return new Response(`Hello ${result.certificate.subject.CN}`);
},
});Plain Header-Iterable Objects
The adapter accepts any object whose headers field iterates as [name, value] tuples. A native Headers instance, a Map, or any custom equivalent works:
const headers = new Map([['client-cert', ':base64DER:']]);
const result = extractClientCertificateFromRequest({ headers }, {
certificateSource: 'cloudflare-rfc9440',
});This is useful for testing without constructing a full Web Request.
CommonJS
const fetchAdapter = await require('client-certificate-auth/fetch').load();
const result = fetchAdapter.extractClientCertificateFromRequest(request, {
certificateSource: 'aws-alb',
});See Also
- Reverse proxy support for the supported presets and encodings, including the new
cloudflare-rfc9440and thechainHeaderoption for RFC 9440 two-header schemes.