x401
Protect your endpoints from agentic traffic with Digital Credentials.
x401 protects your endpoints from agentic traffic through Digital Credentials. When a user requests a protected resource, the endpoint responds with an x401 PROOF-REQUEST header: a Digital Credentials Presentation request.
The user agent delegates the request to the user's Wallet. The user authenticates to their Wallet and approves the credential presentation. The user agent retries the protected resource with a PROOF-RESPONSE header: a Digital Credentials Presentation response.
After cryptographic verification of the presented Verifiable Credential, the user gets access to the protected resource.
x401 is built on top of Digital Credentials. Proof's Cloud Wallet supports x401 through OpenID for Verifiable Presentations (OID4VP).
Proof Cloud Wallet is also available as a Web Component for your website!
Getting started
Ask your agent to protect your endpoints behind x401 using Proof's Cloud Wallet:
Protect my API endpoints behind the x401 protocol, using Proof's Cloud Wallet, following the instructions at https://api.proof.com/.well-known/agent-skills/setup-x401-verifier/SKILL.md
Or follow the step-by-step guide.
1. Install dependencies
npm install @proof.com/x401-node @proof.com/proof-vc-server2. Generate a key pair
The key pair signs your OID4VP requests and your enrollment. It uses P-256.
openssl ecparam -name prime256v1 -genkey -noout -out private-key.pem3. Host an OAuth Client ID Metadata Document
Create an OAuth Client ID Metadata Document (CIMD) and host it at an endpoint you own, publicly accessible over HTTPS. The CLIENT_ID is the exact URL of the document.
import express from "express";
import { readFileSync } from "node:fs";
import { createPrivateKey, createPublicKey } from "node:crypto";
import { agentsTrustListUrl, createClientIdMetadataDocument } from "@proof.com/proof-vc-server";
// The exact URL at which the Client ID Metadata Document is hosted.
const CLIENT_ID = "https://example.com/.well-known/proof-client.json";
const privateKey = createPrivateKey(readFileSync("private-key.pem"));
const publicJwk = createPublicKey(privateKey).export({ format: "jwk" });
// "sandbox" for testing, "production" for production.
const environment = "sandbox";
const app = express();
const document = await createClientIdMetadataDocument({
environment,
clientId: CLIENT_ID,
// Your business name, shown to users when they approve
// a credential presentation for your endpoints.
clientName: "Acme Corporation",
// The agents allowed to use x401 with Proof's Cloud Wallet at your endpoints:
// your own agents (by URL) or Proof's trusted list with `agentsTrustListUrl`.
redirectUris: [agentsTrustListUrl(environment)],
jwks: [publicJwk],
});
app.get("/.well-known/proof-client.json", (req, res) => {
res.json(document);
});
Agents trust list
agentsTrustListUrldefines which trusted agents use x401 and Proof's Cloud Wallet at your endpoints. Proof publishes its list at https://api.proof.com/.well-known/agents-trust-list. See the OID4VP over the Digital Credentials APIexpected_originsparameter for details.
4. Register your OAuth Client ID Metadata Document
Enroll on Proof as an x401 Verifier by registering your CIMD, then follow the instructions received by email to complete your enrollment.
npx @proof.com/proof-vc-server enroll https://example.com/.well-known/proof-client.json --email [email protected] --key private-key.pem --environment sandbox
Email addressThe email address should be on the host of your CIMD, with
www.counting as the apex domain:[email protected]forhttps://example.com/...andhttps://www.example.com/....
5. Protect your endpoint
Create an x401Middleware that inspects the request's PROOF-RESPONSE header and cryptographically verifies the presented vp_token containing the Verifiable Credential.
import { readFileSync } from "node:fs";
import { createPrivateKey, randomUUID } from "node:crypto";
import { verifier as x401, HEADER, DC_API_PROTOCOL } from "@proof.com/x401-node";
import { agentsTrustListUrl, createClient, createVerifier } from "@proof.com/proof-vc-server";
const CLIENT_ID = "https://example.com/.well-known/proof-client.json";
const privateKey = createPrivateKey(readFileSync("private-key.pem"));
const environment = "sandbox";
const proofClient = createClient({
environment,
clientId: CLIENT_ID,
// Signs your OID4VP requests.
useSecuredAuthorizationRequest: true,
privateKeyFactory: () => privateKey,
});
const proofVerifier = createVerifier({ environment });
async function x401Middleware(req, res, next) {
const response = req.get(HEADER.PROOF_RESPONSE);
if (response === undefined) {
// A signed OID4VP request over the Digital Credentials API.
const request = await proofClient.signedDcApiRequest({
// See https://dev.proof.com/docs/verify-a-credential#scopes-and-claims
scope: "urn:proof:params:scope:verifiable-credentials:basic",
// Prevents replay attacks.
nonce: randomUUID(),
// A subset of the redirectUris of your CIMD.
expectedOrigins: [agentsTrustListUrl(environment)],
});
// The PROOF-REQUEST header, following the x401 protocol.
const payload = x401.buildPayload({
credentialRequirements: {
digital: {
requests: [{ protocol: DC_API_PROTOCOL.SIGNED, data: { request } }],
},
},
});
return res
.status(401)
.set(HEADER.PROOF_REQUEST, x401.encodePayload(payload))
.send("x401 PROOF-RESPONSE required");
}
try {
const artifact = x401.decodeResultArtifact(response);
// Verifies the vp_token, issued by Proof's Certificate Authority.
// See https://dev.proof.com/docs/proof-certificate-authority
const presentation = await proofVerifier.verifyVPToken({
encodedVPToken: artifact.credential_result.data.vp_token,
aud: CLIENT_ID,
});
req.credential = presentation.proof_id_default[0];
} catch {
return res.status(401).send("x401 PROOF-RESPONSE invalid");
}
next();
}
app.get("/protected", x401Middleware, (req, res) => {
// Reachable by user agents presenting a valid Verifiable Credential.
res.json({ message: "Access granted", claims: req.credential.toJSON() });
});
Prevent replay attacksCompare
req.credential.getNonce()with the nonce of your OID4VP request.
Claims and scopesLearn about the claims supported by Proof's Verifiable Credentials and how to request them with scopes.
6. Test your endpoint
The endpoint returns HTTP 401 with a proof-request header. Inspect the Digital Credentials Presentation request, then the OID4VP request it carries:
curl -so /dev/null -w '%header{proof-request}' https://example.com/protected \
| jq -R 'gsub("-";"+") | gsub("_";"/") | @base64d | fromjson'curl -so /dev/null -w '%header{proof-request}' https://example.com/protected \
| jq -R 'gsub("-";"+") | gsub("_";"/") | @base64d | fromjson
| .credential_requirements.digital.requests[0].data.request
| split(".")[1] | gsub("-";"+") | gsub("_";"/") | @base64d | fromjson'Then ask your agent to fetch the protected resource with Proof's Cloud Wallet:
Please fetch https://example.com/protected and use my Proof Cloud Wallet following instructions from https://api.proof.com/.well-known/agent-skills/access-x401-resource/SKILL.md
With a valid PROOF-RESPONSE, the endpoint returns 200 and the verified claims.
Go to production
Set environment to "production" and enroll:
npx @proof.com/proof-vc-server enroll https://example.com/.well-known/proof-client.json --email [email protected] --key private-key.pemResources
Updated about 1 hour ago