1. Introduction
This section and its sub-sections are non-normative.
This specification defines an API that enables the use of strong authentication methods in payment flows on the web. It aims to provide the same authentication benefits and user privacy focus as [webauthn-3] with enhancements to meet the needs of payment processing.
Similarly to [webauthn-3], this specification defines two related processes involving a user. The first is § 3 Registration (formerly "enrollment"), where a relationship is created between the user and the Relying Party. The second is § 4 Authentication - the Secure Payment Confirmation payment method, where the user responds to a challenge from the Relying Party (possibly via an intermediary payment service provider) to consent to a specific payment.
It is a goal of this specification to reduce authentication friction during checkout, and one aspect of that is to maximize the number of authentications that the user can perform for a given registration. That is, with consent from the Relying Party, ideally the user could "register once" and authenticate on any merchant origin (and via payment service provider), not just the merchant origin where the user first registered.
To that end, an important feature of Secure Payment Confirmation is that the merchant (or another entity) may initiate the authentication ceremony on the Relying Party’s behalf. The Relying Party must opt-in to allowing this behavior during credential creation.
Functionally, this specification defines a new payment method for the
PaymentRequest API, and adds a WebAuthn Extension to extend
[webauthn-3] with payment-specific datastructures, device binding and to
relax assumptions to allow the API to be called in payment contexts.
1.1. Use Cases
Although [webauthn-3] provides general authentication capabilities for the Web, the following use cases illustrate the value of the payment-specific extension defined in this specification.
We presume that the general use case of cryptographic-based authentication for online transactions is well established.
1.1.1. Cryptographic evidence of transaction confirmation
In many online payment systems, it is common for the entity (e.g., bank) that issues a payment instrument to seek to reduce fraud through authentication. [webauthn-3] and this specification make it possible to use authenticators to cryptographically sign important payment-specific information such as the origin of the merchant and the transaction amount and currency. The bank, as the Relying Party, can then verify the signed payment-specific information as part of the decision to authorize the payment.
If the bank uses plain [webauthn-3], the payment-specific information to be
verified must be stored in the WebAuthn
challenge. This raises several issues:
-
It is a misuse of the
challengefield (which is intended to defeat replay attacks). -
There is no specification for this, so each bank is likely to have to devise its own format for how payment-specific information should be formatted and encoded in the challenge, complicating deployment and increasing fragmentation.
-
Regulations may require evidence that the user was shown and agreed to the payment-specific information. Plain [webauthn-3] does not provide for this display: there is no specified UX associated with information stored in the
challengefield.
These limitations motivate the following Secure Payment Confirmation behaviors:
-
The
challengefield is only used to defeat replay attacks, as with plain [webauthn-3]. -
SPC specifies a format for payment-specific information. This will enable development of generic verification code and test suites.
-
SPC guarantees that the user agent has presented the payment-specific information to the user in a way that a malicious website (or maliciously introduced JavaScript code on a trusted website) cannot bypass.
-
The payment-specific information is included in the
CollectedClientDatadictionary, which cannot be tampered with via JavaScript.
NOTE: Banks and other stakeholders in the payments ecosystem trust payments via browsers sufficiently today using TLS, iframes, and other Web features. The current specification is designed to increase the security and usability of Web payments.
-
1.1.2. Merchant control of authentication
Merchants seek to avoid user drop-off during checkout, in particular by reducing authentication friction. A Relying Party (e.g., a bank) that wishes to use [webauthn-3] to authenticate the user typically does so from an iframe. However, merchants would prefer to manage the user experience of authenticating the user while still enabling the Relying Party to verify the results of authentication.
This limitation motivates the following Secure Payment Confirmation behavior:
-
With SPC, other parties than the Relying Party can use authentication credentials on behalf of the Relying Party. The Relying Party can then verify the authentication results.
An additional benefit of this feature to Relying Parties is that they no longer need to build their own front-end experiences for authentication. Instead, payment service providers are likely to build them on behalf of merchants.
NOTE: Relying Parties that wish to provide the authentication user experience may still do so using SPC from an iframe.
1.1.3. Cryptographic evidence of device binding
In the payments industry a signal of device possession plays an important role as a second factor. WebAuthn allows for synced passkeys where one credential is available on multiple devices (Web Authentication § 1.2.1 Consumer with Multi-Device Credentials). Although syncing improves the user experience for login use cases, concerns have been raised that synced passkeys alone do not satisfy device possession requirements in some regulatory environments.
These concerns motivate the inclusion of auxiliary public private key pairs created by the user agent where the private key only resides on (and is used on) one device. Such a key and its use in SPC is referred to as a browser bound key.
1.2. Sample API Usage Scenarios
In this section, we walk through some scenarios for Secure Payment Confirmation and the corresponding sample code for using this API. Note that these are example flows and do not limit the scope of how the API can be used.
1.2.1. Registration during a checkout
This is a first-time flow, in which a new credential is created and stored by an issuing bank during a checkout by the user on some merchant.
-
The user visits
merchant.example, selects an item to purchase, and proceeds to the checkout flow. They enter their payment instrument details, and indicate that they wish to pay (e.g., by pressing a "Pay" button). -
The merchant communicates out-of-band (e.g., using another protocol) with the bank that issued the payment instrument. The issuing bank requests verification of the user, and provides a bank-controlled URL for the merchant to open in an iframe.
-
The merchant opens an iframe to
bank.example, with theallowattribute set to "publickey-credentials-create". -
In the iframe, the issuing bank confirms the user’s identity via a traditional means (e.g., SMS OTP). After confirmation, the bank invites the user to register in SPC authentication for future payments.
-
The user consents (e.g., by clicking an "Register" button in the bank UX), and the bank runs code in the iframe (see example below).
-
The user goes through a WebAuthn registration flow. A new credential is created and returned to the issuing bank who stores it in their server-side database associated with the user and payment instrument(s).
-
The verification completes; the bank iframe closes and the merchant finishes the checkout process for the user.
Sample code for registering the user in this way follows:
if ( ! window. PublicKeyCredential) { /* Client not capable. Handle error. */ } const publicKey= { // The challenge should be created by the bank server and sent to the iframe. challenge: new Uint8Array([ 21 , 31 , 105 /* 29 more random bytes generated by the server */ ]), // Relying Party: rp: { name: "Fancy Bank" , }, // User: user: { // Part of WebAuthn. This information is not required by SPC // but may be used by the bank server to identify this user in // future transactions. Inconsistent values for the same user // can result in the creation of multiple credentials for the user // and thus potential UX friction due to credential selection. id: Uint8Array. from ( window. atob( "MIIBkzCCATigAwIBAjCCAZMwggE4oAMCAQIwggGTMII=" ), c=> c. charCodeAt( 0 )), name: "jane.doe@email.example" , displayName: "Jane Doe" , }, // In this example the Relying Party accepts either an ES256 or RS256 // credential, but prefers an ES256 credential. pubKeyCredParams: [ { type: "public-key" , alg: - 7 // "ES256" }, { type: "public-key" , alg: - 257 // "RS256" } ], authenticatorSelection: { userVerification: "required" , residentKey: "required" , authenticatorAttachment: "platform" , }, timeout: 360000 , // 6 minutes // Indicate that this is an SPC credential. This is currently required so // that the browser knows this credential relates to SPC. It also enables // credential creation in a cross-origin iframe, which is required for this // example. // // A future version of the spec may remove the need for this extension. 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" } ] } } }; // Note: The following call will cause the authenticator to display UI. navigator. credentials. create({ publicKey}) . then( function ( newCredentialInfo) { // Send new credential info to server for verification and registration. }). catch ( function ( err) { // No acceptable authenticator or user refused consent. Handle appropriately. });
1.2.2. Authentication on merchant site
This is the flow when a user with an already registered credential is performing a transaction and the issuing bank and merchant wish to use Secure Payment Confirmation.
-
The user visits
merchant.example, selects an item to purchase, and proceeds to the checkout flow. They enter their payment instrument details, and indicate that they wish to pay (e.g., by pressing a "Pay" button). -
The merchant communicates out-of-band with the issuing bank of the payment instrument (e.g., using another protocol). The issuing bank requests verification of the user, and at the same time informs the merchant that it accepts SPC by providing the information necessary to use the API. This information includes a challenge and any credential IDs associated with this user and payment instrument(s).
-
The merchant runs the example code shown below.
-
The user agrees to the payment-specific information displayed in the SPC UX, and performs a subsequent WebAuthn authentication ceremony. The signed cryptogram is returned to the merchant (including browser bound key outputs in
AuthenticationExtensionsPaymentOutputs). -
The merchant communicates the signed cryptogram to the issuing bank out-of-band. The issuing bank verifies the cryptogram, and knows that the user is valid, what payment-specific information has been displayed, and that the user has consented to the transaction. The issuing bank authorizes the transaction and the merchant finishes the checkout process for the user. The issuing bank stores the browser bound key’s public key,
browserBoundPublicKey.
The sample code for authenticating the user follows. Note that the example code presumes access to await/async, for easier to read promise handling.
/* securePaymentConfirmationAvailability indicates whether the browser */ /* supports SPC. It does not indicate whether the user has a credential */ /* ready to go on this device. */ const spcAvailable= PaymentRequest&& PaymentRequest. securePaymentConfirmationAvailability&& ( await PaymentRequest. securePaymentConfirmationAvailability()) === 'available' ; if ( ! spcAvailable) { /* Browser does not support SPC; merchant should fallback to traditional flows. */ } 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 */ ]), instrument: { displayName: "FancyBank Platinum Card" , details: "****1234 | 01/29" , icon: "https://fancybank.example/card-art.png" , }, payeeName: "Merchant Shop" , payeeOrigin: "https://merchant.example" , paymentEntitiesLogos: [ { url: "https://fancybank.example/logo.png" , label: "Fancy Bank" , }, { url: "https://securenetwork.example/logo.png" , label: "Secure Network" , }, ], // Caller’s requested localized experience locale: [ "en" ], timeout: 360000 , // 6 minutes // 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" } ] }], { total: { label: "Total" , amount: { currency: "USD" , value: "5.00" , }, }, }); try { const response= await request. show(); await response. complete( 'success' ); // response.data is a PublicKeyCredential, with a clientDataJSON that // contains the transaction data for verification by the issuing bank. /* send response.data to the issuing bank for verification */ } catch ( err) { /* SPC cannot be used; merchant should fallback to traditional flows */ }