How to implement post-quantum hybrid authentication in Express¶
Add dual-signature hybrid authentication middleware to an Express.js server, combining classical Ed25519 and NIST FIPS 204 ML-DSA-65 to protect APIs against quantum cryptanalysis.
The problem¶
Single-algorithm tokens leave web services exposed to sudden algorithmic breaks. If your service uses only classical RSA or ECDSA, an attacker with a cryptographically relevant quantum computer can forge tokens. If your service switches prematurely to a post-quantum algorithm with unproven implementation robustness, an unexpected classical vulnerability could compromise security. A hybrid authentication scheme requires every bearer token to verify under both a classical and a post-quantum signature algorithm simultaneously.
Install¶
Complete working code¶
express_hybrid_auth.mjs
#!/usr/bin/env node
/**
* Express Recipe: Post-Quantum + Classical Hybrid Authentication Middleware.
* Combines classical Ed25519 with NIST FIPS 204 ML-DSA-65 signatures.
*/
import express from 'express';
import crypto from 'node:crypto';
import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';
// 1. Generate Classical (Ed25519) and Post-Quantum (ML-DSA-65) server keys
const classicalKeyPair = crypto.generateKeyPairSync('ed25519');
const pqcSeed = new Uint8Array(32).fill(42); // deterministic seed for test reproducibility
const pqcKeys = ml_dsa65.keygen(pqcSeed);
// Helper: Issue a dual/hybrid bearer token
function issueHybridToken(subject) {
const payload = JSON.stringify({ sub: subject, iat: Math.floor(Date.now() / 1000) });
const payloadBytes = Buffer.from(payload, 'utf8');
// Classical signature (Ed25519)
const classicalSig = crypto.sign(null, payloadBytes, classicalKeyPair.privateKey);
// Post-quantum signature (ML-DSA-65)
const pqcSig = ml_dsa65.sign(payloadBytes, pqcKeys.secretKey);
return {
payload: payloadBytes.toString('base64url'),
classicalSig: classicalSig.toString('base64url'),
pqcSig: Buffer.from(pqcSig).toString('base64url'),
};
}
// 2. Hybrid Authentication Express Middleware
function hybridAuthMiddleware(req, res, next) {
const authHeader = req.headers['authorization'];
if (!authHeader || !authHeader.startsWith('Hybrid ')) {
return res.status(401).json({ error: 'Missing or malformed Authorization header' });
}
try {
const raw = Buffer.from(authHeader.slice(7), 'base64url').toString('utf8');
const { payload, classicalSig, pqcSig } = JSON.parse(raw);
const payloadBytes = Buffer.from(payload, 'base64url');
// Verify Classical (Ed25519) signature
const classicalValid = crypto.verify(
null,
payloadBytes,
classicalKeyPair.publicKey,
Buffer.from(classicalSig, 'base64url')
);
if (!classicalValid) {
return res.status(401).json({ error: 'Invalid classical signature' });
}
// Verify Post-Quantum (ML-DSA-65) signature
const pqcValid = ml_dsa65.verify(
Buffer.from(pqcSig, 'base64url'),
payloadBytes,
pqcKeys.publicKey
);
if (!pqcValid) {
return res.status(401).json({ error: 'Invalid post-quantum signature' });
}
req.user = JSON.parse(payloadBytes.toString('utf8'));
next();
} catch (err) {
return res.status(401).json({ error: 'Malformed hybrid token' });
}
}
// 3. Setup Express app
const app = express();
app.use(express.json());
app.get('/api/protected', hybridAuthMiddleware, (req, res) => {
res.json({ authenticated: true, user: req.user.sub, mode: 'ML-DSA-65+Ed25519' });
});
// 4. Run test queries against listening server
const server = app.listen(0, async () => {
const port = server.address().port;
const baseUrl = `http://127.0.0.1:${port}/api/protected`;
// Test A: Valid Hybrid Token
const validToken = issueHybridToken('alice@example.com');
const validHeader = 'Hybrid ' + Buffer.from(JSON.stringify(validToken)).toString('base64url');
const resA = await fetch(baseUrl, { headers: { Authorization: validHeader } });
const dataA = await resA.json();
console.log(`valid hybrid auth: HTTP ${resA.status} authenticated: ${dataA.authenticated}`);
// Test B: Tampered PQC Signature
const badPqcToken = { ...validToken, pqcSig: 'AAAA' + validToken.pqcSig.slice(4) };
const badPqcHeader = 'Hybrid ' + Buffer.from(JSON.stringify(badPqcToken)).toString('base64url');
const resB = await fetch(baseUrl, { headers: { Authorization: badPqcHeader } });
console.log(`tampered pqc credential: HTTP ${resB.status} Unauthorized`);
// Test C: Tampered Classical Signature
const badClassToken = { ...validToken, classicalSig: 'BBBB' + validToken.classicalSig.slice(4) };
const badClassHeader = 'Hybrid ' + Buffer.from(JSON.stringify(badClassToken)).toString('base64url');
const resC = await fetch(baseUrl, { headers: { Authorization: badClassHeader } });
console.log(`tampered classical credential: HTTP ${resC.status} Unauthorized`);
server.close(() => {
console.log('server closed');
});
});
express_hybrid_auth.ts
import express, { type Request, type Response, type NextFunction } from 'express';
import crypto from 'node:crypto';
import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';
export interface HybridTokenPayload {
sub: string;
iat: number;
}
export interface HybridTokenContainer {
payload: string; // base64url JSON
classicalSig: string; // base64url Ed25519 signature
pqcSig: string; // base64url ML-DSA-65 signature
}
export function hybridAuthMiddleware(
classicalPubKey: crypto.KeyObject,
pqcPubKey: Uint8Array
) {
return (req: Request, res: Response, next: NextFunction): void => {
const header = req.headers['authorization'];
if (!header || !header.startsWith('Hybrid ')) {
res.status(401).json({ error: 'Missing or malformed Authorization header' });
return;
}
try {
const raw = Buffer.from(header.slice(7), 'base64url').toString('utf8');
const token: HybridTokenContainer = JSON.parse(raw);
const payloadBytes = Buffer.from(token.payload, 'base64url');
// 1. Verify Classical (Ed25519) signature
const isClassicalValid = crypto.verify(
null,
payloadBytes,
classicalPubKey,
Buffer.from(token.classicalSig, 'base64url')
);
if (!isClassicalValid) {
res.status(401).json({ error: 'Invalid classical signature' });
return;
}
// 2. Verify Post-Quantum (ML-DSA-65) signature
const isPqcValid = ml_dsa65.verify(
Buffer.from(token.pqcSig, 'base64url'),
payloadBytes,
pqcPubKey
);
if (!isPqcValid) {
res.status(401).json({ error: 'Invalid post-quantum signature' });
return;
}
(req as any).user = JSON.parse(payloadBytes.toString('utf8')) as HybridTokenPayload;
next();
} catch {
res.status(401).json({ error: 'Malformed hybrid token structure' });
}
};
}
Output:
valid hybrid auth: HTTP 200 authenticated: true
tampered pqc credential: HTTP 401 Unauthorized
tampered classical credential: HTTP 401 Unauthorized
server closed
How it works¶
- Composite Token Issuance. The authentication server signs the token payload twice: first with classical Ed25519 (64-byte signature), and second with ML-DSA-65 (3,309-byte signature).
- Strict Dual Verification. The Express middleware evaluates both signatures sequentially. If either signature fails verification or is omitted, the request is immediately rejected with HTTP 401 Unauthorized.
- Defense in Depth. Security holds so long as either Ed25519 remains resistant to classical attacks or ML-DSA-65 remains resistant to quantum attacks.
Things to know¶
- Header Sizes: ML-DSA-65 signatures are 3,309 bytes. Ensure your reverse proxy (e.g. Nginx, Envoy, Cloudflare) is configured to permit
client_header_buffer_size 16k;ormax_header_size: 16384. - Pure JavaScript:
@noble/post-quantumhas zero native compilation dependencies, making it safe for deployment across containerized Node.js, Alpine Linux, or serverless runtimes.