Secure Payment Confirmation

Editor’s Draft,

More details about this document
This version:
https://w3c.github.io/secure-payment-confirmation/
Latest published version:
https://www.w3.org/TR/secure-payment-confirmation/
Implementation Report:
https://wpt.fyi/results/secure-payment-confirmation
Feedback:
GitHub
Inline In Spec
Editor:
(Google)
Former Editor:
(Google)
Contributors:
Ian Jacobs (W3C)
Nick Burris (Google)
Darwin Yang (Google)
Test Suite:
https://wpt.fyi/results/secure-payment-confirmation/

Abstract

Secure Payment Confirmation (SPC) is a Web API to support streamlined authentication during a payment transaction. It is designed to scale authentication across merchants, to be used within a wide range of authentication protocols, and to produce cryptographic evidence that the user has confirmed transaction details.

Status of this document

This section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at https://www.w3.org/TR/.

This document was published by the Web Payments Working Group as an Editors' Draft. This document is intended to become a W3C Recommendation. Feedback and comments on this specification are welcome. Please use Github issues. Discussions may also be found in the public-payments-wg@w3.org archives.

Publication as an Editors' Draft does not imply endorsement by the W3C Membership. This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.

This document was produced by a group operating under the W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.

This document is governed by the 12 June 2023 W3C Process Document.

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:

  1. It is a misuse of the challenge field (which is intended to defeat replay attacks).

  2. 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.

  3. 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 challenge field.

These limitations motivate the following Secure Payment Confirmation behaviors:

  1. The challenge field is only used to defeat replay attacks, as with plain [webauthn-3].

  2. SPC specifies a format for payment-specific information. This will enable development of generic verification code and test suites.

  3. 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 CollectedClientData dictionary, 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:

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.

  1. 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).

  2. 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.

  3. The merchant opens an iframe to bank.example, with the allow attribute set to "publickey-credentials-create".

  4. 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.

  5. 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).

  6. 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).

  7. 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.

  1. 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).

  2. 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).

  3. The merchant runs the example code shown below.

  4. 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).

  5. 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 */
}

2. Terminology