#1097: Browser Bound Keys for Secure Payment Confirmation
Discussions
Log in to see TAG-private discussions.
Discussed
Jun 9, 2025 (See Github)
punt to next week
Discussed
Jun 16, 2025 (See Github)
Marcos: Haven't looked at this one. Not familiar with the device-bound stuff. Will need to learn more about this feature.
Hadley: We'll need to replace Dan on this one.
Yoav: Could help out.
Discussed
Jun 23, 2025 (See Github)
Yoav: I asked some questions regarding the API shape. Not sure if I'm well-versed enough in WebAuthn to know if this is idiomatic in terms of API shape. Generally seems fine, but not sure if the API shape makes sense.
Marcos: Somewhat hesitant to say something here; it requires in-depth knowledge of SPC and BBC. Would advise to take to WebAuthn WG. It came form Web Payments. They will provide more meaningful feedback.
Matthew: Could you draft a comment to that effect and we can see what sort of support we get?
Marcos: Yes; Yoav: works for you?
Yoav: Yes, makes sense what they're trying to do, but the WebAuthn folks will know about the API shape.
Discussed
Jun 23, 2025 (See Github)
Jeffrey: Skip today
Comment by @yoavweiss Jun 24, 2025 (See Github)
Hey @pejic!
The explainer covers the motivation well, but doesn't really explain how web developers will interact with the feature. What's the API shape through which developers can ask the browser to create the BBK? How will they receive the relevant data?
Something more concrete would be very useful for reviewing the feature :)
Comment by @pejic Jun 24, 2025 (See Github)
Hi @yoavweiss!
Thanks for taking a look. I have amended the explainer with an API Shape section (copied below) providing examples for both registration (passkey enrollment) and assertion (payment).
API Shape
When API users want to create a passkey for payments they register via navigator.credential.create
setting the payment extension ("isPayment": true
), and now the public key is returned with the browser bound public key in clientDataJSON and the signature in the payment extension's outputs. E.g.,
credential = await navigator.credentials.create({
challenge: /* random Uint8Array generated by the server */,
...
extensions: {
"payment": {
isPayment: true,
// An optional list of allowed algorithms. When not present or empty, the
// pubKeyCredparams are used defaulting to ES256 and RS256. In this
// example ES256 and RS256 are allowed and RS256 is preferred.
browserBoundPubKeyCredParams: [
{
type: "public-key",
alg: -257 // "RS256"
},
{
type: "public-key",
alg: -7 // "ES256"
}
]
}
}
});
clientData = JSON.parse(arrayBufferToString(credential.response.clientDataJSON));
// browserBoundPublicKey is a base64url encoded - COSE_key representation of the public key
const bbkPublicKeyBase64UrlEncoded = clientData.payment.browserBoundPublicKey;
// The signature is an array buffer with a format specific to the algorithm.
const signatureArrayBuffer = credential.getClientExtenionResults().payment.browserBoundSignature.signature;
// The credential is sent to the server.
// Later the server verifies the cryptographic signature over clientDataJSON using browserBoundPublicKey.
When API users want to assert payments, they use the secure payment confirmation API (via PaymentRequest) and now the browser bound public key and signature are returned in the response, the public key in clientDataJSON and the signature in payment extension outputs (same as the registration above): E.g.,
const request = new PaymentRequest([{
supportedMethods: "secure-payment-confirmation",
data: {
// List of credential IDs obtained from the bank.
credentialIds,
rpId: "fancybank.example",
// The challenge is also obtained from the bank.
challenge: new Uint8Array([21,31,105 /* 29 more random bytes generated by the bank */]),
...
// An optional list of allowed algorithms defaulting to ES256 and RS256.
// In this example ES256 and RS256 are allowed and ES256 is preferred.
// Browser bound keys are not created when already present, so this
// list is only used when the browser bound key does need to be
// created.
browserBoundPubKeyCredParams: [
{
type: "public-key",
alg: -7 // "ES256"
},
{
type: "public-key",
alg: -257 // "RS256"
}
]
}], ...);
const response = await request.show();
await response.complete('success');
const credential = response.details;
clientData = JSON.parse(arrayBufferToString(credential.response.clientDataJSON));
// browserBoundPublicKey is a base64url encoded - COSE_key representation of the public key
const bbkPublicKeyBase64UrlEncoded = clientData.payment.browserBoundPublicKey;
// The signature is an array buffer with a format specific to the algorithm.
const signatureArrayBuffer = credential.getClientExtenionResults().payment.browserBoundSignature.signature;
// The credential is sent to the server.
// Later the server verifies the cryptographic signature over clientDataJSON using browserBoundPublicKey.
OpenedMay 20, 2025
Zdravo TAG!
I'm requesting a TAG review of Browser Bound Keys, a change to Secure Payment Confirmation (802, 763, 675, 544).
Add device-binding-like capabilities, in the form of browser bound keys (BBKs), to Secure Payment Confirmation without relying on WebAuthn (at either the client or authenticator level)
Further details: