Secure Payment Confirmation

W3C Working Draft,

More details about this document
This version:
https://www.w3.org/TR/2022/WD-secure-payment-confirmation-20220810/
Latest published version:
https://www.w3.org/TR/secure-payment-confirmation/
Editor's Draft:
https://w3c.github.io/secure-payment-confirmation/
Previous Versions:
History:
https://www.w3.org/standards/history/secure-payment-confirmation
Feedback:
GitHub
Editors:
(Google)
(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. 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/.

The Web Payments Working Group maintains a list of all bug reports that the group has not yet addressed. This draft highlights some of the pending issues that are still to be discussed in the working group. No decision has been taken on the outcome of these issues including whether they are valid. Pull requests with proposed specification text for outstanding issues are strongly encouraged.

This document was published by the Web Payments Working Group as a Working Draft using the Recommendation track. This document is intended to become a W3C Recommendation.

Publication as a Working Draft does not imply endorsement by W3C and its Members. 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 1 August 2017 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 2 November 2021 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, 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 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.

Note: These sections are still a WIP.

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. Registration in a third-party iframe

If a bank wishes to use [webauthn-3] as the Relying Party, that specification requires the bank to register the user in a first party context. Registration can happen outside of a transaction while the user is visiting the bank’s site. It is also useful to be able to register the user during a transaction, but any registration that interrupts the payment journey creates a risk of transaction abandonment.

This limitation motivates the following Secure Payment Confirmation behavior:

  1. SPC supports cross-origin registration from an iframe in a third-party context. For instance, this registration might take place following some other identity and verification (ID&V) flow (e.g., SMS OTP).

1.1.3. 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.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.com, 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.com, with the allow attribute set to "payment".

  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@example.com",
    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,
    }
  }
};

// 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.com, 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.

  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 sample code for authenticating the user follows. Note that the example code presumes access to await/async, for easier to read promise handling.

if (!window.PaymentRequest) { /* PaymentRequest not available; 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.com",

    // The challenge is also obtained from the bank.
    challenge: new Uint8Array([21,31,105 /* 29 more random bytes generated by the bank */]),

    instrument: {
      displayName: "Fancy Card ****1234",
      icon: "https://fancybank.com/card-art.png",
    },

    payeeName: "Merchant Shop",
    payeeOrigin: "https://merchant.com",

    timeout: 360000,  // 6 minutes
  }], {
    total: {
      label: "Total",
      amount: {
        currency: "USD",
        value: "5.00",
      },
    },
  });

  /* canMakePayment indicates whether the browser supports SPC. */
  /* canMakePayment does not indicate whether the user has a credential */
  /* ready to go on this device. */

try {
  const canMakePayment = await request.canMakePayment();
  if (!canMakePayment) { throw new Error('Cannot make payment'); }

  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

SPC Credential

A WebAuthn credential that can be used for the behaviors defined in this specification. When an SPC Credential is to be used by a party other than the Relying Party, the Relying Party must explicitly opt in by declaring the credential for SPC at creation time.

This specification does not intend to limit how SPC credentials may (or may not) be used by a Relying Party for other authentication flows (e.g., login).

Note: The current version of this specification requires the Relying Party to explicitly opt in for a credential to be used in either a first-party or third-party context. Longer-term, our intention is that all WebAuthn credentials will be usable for SPC in a first-party context (e.g., on the Relying Party’s domain) and opt-in will only be required to allow a credential to be used by a third-party.

Steps to silently determine if a credential is SPC-enabled

An as-yet undefined process by which a user agent can, given a Relying Party Identifier and a credential ID, silently (i.e., without user interaction) determine if the credential represented by that ID is an SPC Credential.

NOTE: See WebAuthn issue 1667.

Steps to silently determine if a credential is available for the current device

An as-yet undefined process by which a user agent can, given a Relying Party Identifier and a credential ID, silently (i.e., without user interaction) determine if the credential represented by that credential ID is available for the current device (i.e., could be successfully used as part of a WebAuthn Get call).

This allows the user agent to only conditionally display the transaction UX to the user if there is some chance that they can successfully complete the transaction.

NOTE: This property will likely require that SPC Credentials be discoverable; as such this specification currently encodes that as a requirement.

NOTE: This property is very similar to that which is required for the WebAuthn Conditional UI Proposal. It is likely that both it and SPC could be supported by the same underlying API.

3. Registration

To register a user for Secure Payment Confirmation, relying parties should call navigator.credentials.create(), with the payment WebAuthn Extension specified.

Tests

Note: In this specification we define an extension in order to allow (1) credential creation in a cross-origin iframe (which WebAuthn does not yet allow) and (2) the browser to cache SPC credentials in the absence of Conditional UI. If these capabilities are available in future versions of WebAuthn, we may remove the requirement for the extension from SPC. Note that SPC credentials (with the extension) are otherwise full-fledged WebAuthn credentials.

4. Authentication

To authenticate a payment via Secure Payment Confirmation, this specification defines a new payment method, "secure-payment-confirmation". This payment method confirms the transaction with the user and then performs an authentication ceremony to authenticate the user and create a signed blob representing the authentication ceremony.

At a high level, authentication for Secure Payment Confirmation is similar to [webauthn-3], with one major conceptual shift. Secure Payment Confirmation allows a third-party (e.g., the merchant) to trigger an authentication ceremony on behalf of the Relying Party, passing in credentials that it has obtained from the Relying Party on some other unspecified channel. See § 1.1.3 Merchant control of authentication.

Tests

This test file tests inherited behavior from the PaymentRequest specification; that a user activation is required to call show() (and thus to trigger SPC authentication). We test it explicitly for SPC to make it clear that this behavior is desirable even if the API shape for SPC changes in the future.


4.1. Payment Method: Secure Payment Confirmation

This specification defines a new payment handler, the Secure Payment Confirmation payment handler, which handles requests to authenticate a given payment.

NOTE: To quickly support an initial SPC experiment, this API was designed atop existing implementations of the Payment Request and Payment Handler APIs. There is now general agreement to explore a design of SPC independent of Payment Request. We therefore expect (without a concrete timeline) that SPC will move away from its Payment Request origins. For developers, this should improve feature detection, invocation, and other aspects of the API.

TODO: This specification also needs to monkey-patch step 12 of PaymentRequest’s constructor somehow, to enforce that when "secure-payment-confirmation" is used, exactly one method is given.

4.1.1. Payment Method Identifier

The standardized payment method identifier for the Secure Payment Confirmation payment handler is "secure-payment-confirmation".

4.1.2. Monkey-patch to [payment-method-id]

Add the following to the registry of standardized payment methods in [payment-method-id]:

"secure-payment-confirmation"

The Secure Payment Confirmation specification.

4.1.3. SecurePaymentConfirmationRequest Dictionary

dictionary SecurePaymentConfirmationRequest {
    required BufferSource challenge;
    required USVString rpId;
    required FrozenArray<BufferSource> credentialIds;
    required PaymentCredentialInstrument instrument;
    unsigned long timeout;
    DOMString payeeName;
    USVString payeeOrigin;
    AuthenticationExtensionsClientInputs extensions;
};

The SecurePaymentConfirmationRequest dictionary contains the following members:

challenge member, of type BufferSource

A random challenge that the relying party generates on the server side to prevent replay attacks.

rpId member, of type USVString

The Relying Party Identifier of the credentials.

credentialIds member, of type FrozenArray<BufferSource>

The list of credential identifiers for the given instrument.

instrument member, of type PaymentCredentialInstrument

The description of the instrument name and icon to display during registration and to be signed along with the transaction details.

timeout member, of type unsigned long

The number of milliseconds before the request to sign the transaction details times out. At most 1 hour.

payeeName member, of type DOMString

The display name of the payee that this SPC call is for (e.g., the merchant). Optional, may be provided alongside or instead of payeeOrigin.

payeeOrigin member, of type USVString

The origin of the payee that this SPC call is for (e.g., the merchant). Optional, may be provided alongside or instead of payeeName.

extensions member, of type AuthenticationExtensionsClientInputs

Any WebAuthn extensions that should be used for the passed credential(s). The caller does not need to specify the payment extension; it is added automatically.

4.1.4. Payment Method additional data type

The payment method additional data type for this payment method is SecurePaymentConfirmationRequest.

4.1.5. Steps to validate payment method data

The steps to validate payment method data for this payment method, for an input SecurePaymentConfirmationRequest data, are:

Tests
  1. If data["credentialIds"] is empty, throw a RangeError.

  2. For each id in data["credentialIds"]:

    1. If id is empty, throw a RangeError.

  3. If data["challenge"] is null or empty, throw a TypeError.

  4. If data["instrument"]["displayName"] is empty, throw a TypeError.

  5. If data["instrument"]["icon"] is empty, throw a TypeError.

  6. Run the URL parser on data["instrument"] ["icon"]. If this returns failure, throw a TypeError.

  7. If data["rpId"] is not a valid domain, throw a TypeError.

  8. If both data["payeeName"] and data["payeeOrigin"] are omitted, throw a TypeError.

  9. If either of data["payeeName"] or data["payeeOrigin"] is present and empty, throw a TypeError.

  10. If data["payeeOrigin"] is present:

    1. Let parsedURL be the result of running the URL parser on data["payeeOrigin"].

    2. If parsedURL is failure, then throw a TypeError.

    3. If parsedURL’s scheme is not "https", then throw a TypeError.

4.1.6. Steps to check if a payment can be made

The steps to check if a payment can be made for this payment method, for an input SecurePaymentConfirmationRequest data, are:

Tests
  1. If data["payeeOrigin"] is present:

    1. Let parsedURL be the result of running the URL parser on data["payeeOrigin"].

    2. Assert that parsedURL is not failure.

    3. Assert that parsedURL’s scheme is "https".

    NOTE: These pre-conditions were previously checked in the steps to validate payment method data.

    1. Set data["payeeOrigin"] to the serialization of parsedURL’s origin.

  2. Fetch the image resource for the icon, passing «["src" → data["instrument"]["icon"]]» for image. If this fails:

    1. If data["instrument"]["iconMustBeShown"] is true, then return false.

    2. Otherwise, set data["instrument"]["icon"] to an empty string.

      Note: This lets the RP know that the specified icon was not shown, as the output instrument will have an empty icon string.

    Note: The image resource must be fetched whether or not any credential matches, to defeat attempts to probe for credential existence.

  3. For each id in data["credentialIds"]:

    1. Run the steps to silently determine if a credential is available for the current device and the steps to silently determine if a credential is SPC-enabled, passing in data["rpId"] and id. If the result of either of these is false, remove id from data["credentialIds"].

  4. If data["credentialIds"] is now empty, return false. The user agent must maintain authentication ceremony privacy and not leak this lack of matching credentials to the caller, by:

    1. Not allowing the caller to perform a timing attack on this outcome versus the user declining to authenticate on the transaction confirmation UX, e.g., by presenting an alternative interstitial that the user must interact with.

    2. Rejecting the show() promise with a "NotAllowedError" DOMException.

  5. Return true.

4.1.7. Displaying a transaction confirmation UX

To avoid restricting User Agent implementation choice, this specification does not require a User Agent to display a particular user interface when PaymentRequest.show() is called and the Secure Payment Confirmation payment handler is selected. However, so that a Relying Party can trust the information included in CollectedClientPaymentData, the User Agent MUST ensure that the following is communicated to the user and that the user’s consent is collected for the authentication:

If the current transaction automation mode is not "none", the user agent should first verify that it is in an automation context (see WebDriver’s Security considerations). The user agent should then bypass the above communication of information and gathering of user consent, and instead do the following based on the value of the current transaction automation mode:

"autoaccept"

Act as if the user has seen the transaction details and accepted the authentication.

"autoreject"

Act as if the user has seen the transaction details and rejected the authentication.

4.1.8. Steps to respond to a payment request

The steps to respond to a payment request for this payment method, for a given PaymentRequest request and SecurePaymentConfirmationRequest data, are:

  1. Let topOrigin be the top-level origin of the relevant settings object of request.

  2. Let payment be a new a AuthenticationExtensionsPaymentInputs dictionary, whose fields are:

    isPayment

    The boolean value true.

    rp

    data["rpId"]

    topOrigin

    topOrigin

    payeeName

    data["payeeName"] if it is present, otherwise omitted.

    payeeOrigin

    data["payeeOrigin"] if it is present, otherwise omitted.

    total

    request.[[details]]["total"]

    instrument

    data["instrument"]

  3. Let extensions be a new AuthenticationExtensionsClientInputs dictionary whose payment member is set to payment, and whose other members are set from data["extensions"].

  4. Let publicKeyOpts be a new PublicKeyCredentialRequestOptions dictionary, whose fields are:

    challenge

    data["challenge"]

    timeout

    data["timeout"]

    rpId

    data["rpId"]

    userVerification

    required

    extensions

    extensions

    Note: This algorithm hard-codes "required" as the value for userVerification, because that is what Chrome’s initial implementation supports. The current limitations may change. The Working Group invites implementers to share use cases that would benefit from support for other values (e.g., "preferred" or "discouraged").

  5. For each id in data["credentialIds"]:

    1. Let descriptor be a new PublicKeyCredentialDescriptor dictionary, whose fields are:

      type

      public-key

      id

      id

      transports

      A sequence of length 1 whose only member is internal.

    2. Append descriptor to publicKeyOpts["allowCredentials"].

  6. Let outputCredential be the result of running the algorithm to Request a Credential, passing «["publicKey" → publicKeyOpts]».

    Note: Chrome’s initial implementation does not pass the full data.credentialIds list to Request a Credential. Instead, it chooses one credential in the list that matches the current device and passes only that in.

    Note: This triggers [webauthn-3]'s Get behavior

  7. Return outputCredential.

5. WebAuthn Extension - "payment"

This client registration extension and authentication extension indicates that a credential is either being created for or used for Secure Payment Confirmation, respectively.

For registration, this extension relaxes the WebAuthn requirements to allow credential creation in a cross-origin iframe, and also allows the browser to identify and cache Secure Payment Confirmation credentials. For authentication, this extension allows a third-party to perform an authentication ceremony on behalf of the Relying Party, and also adds transaction information to the signed cryptogram.

Notably, a website should not call navigator.credentials.get() with this extension directly; for authentication the extension can only be accessed via PaymentRequest with a "secure-payment-confirmation" payment method.

Tests

This test does not directly correspond to a spec line, but instead tests that authentication can be triggered from inside a cross-origin iframe. That behavior is specified by the lack of any line forbidding it.


Extension identifier

payment

Operation applicability

Registration and authentication

Client extension input
partial dictionary AuthenticationExtensionsClientInputs {
  AuthenticationExtensionsPaymentInputs payment;
};

dictionary AuthenticationExtensionsPaymentInputs {
  boolean isPayment;

  // Only used for authentication.
  USVString rp;
  USVString topOrigin;
  DOMString payeeName;
  USVString payeeOrigin;
  PaymentCurrencyAmount total;
  PaymentCredentialInstrument instrument;
};
isPayment member, of type boolean

Indicates that the extension is active.

TODO: Find a better way to do this. Needed currently because other members are auth-time only.
rp member, of type USVString

The Relying Party id of the credential(s) being used. Only used at authentication time; not registration.

topOrigin member, of type USVString

The origin of the top-level frame. Only used at authentication time; not registration.

payeeName member, of type DOMString

The payee name, if present, that was displayed to the user. Only used at authentication time; not registration.

payeeOrigin member, of type USVString

The payee origin, if present, that was displayed to the user. Only used at authentication time; not registration.

total member, of type PaymentCurrencyAmount

The transaction amount that was displayed to the user. Only used at authentication time; not registration.

instrument member, of type PaymentCredentialInstrument

The instrument details that were displayed to the user. Only used at authentication time; not registration.

Client extension processing (registration)

Note: Reading [webauthn-3] literally, these steps don’t work; extensions are injected at step 12 of [[Create]] and cannot really modify anything. However other extensions ignore that entirely and assume they can modify any part of any WebAuthn algorithm!

When creating a new credential:

  1. Modify step 2 (the check for sameOriginWithAncestors) as follows:

    Note: This allows for creating SPC credentials in a cross-origin iframe, as long as the correct permission policy is set (see § 7 Permissions Policy integration). A transient activation is also required in this case to mitigate privacy risks; see § 11.1 Registration in a Cross-Origin iframe.

  2. After step 3, insert the following step:

Client extension processing (authentication)

When making an assertion with a AuthenticationExtensionsPaymentInputs extension_inputs:

  1. If not in a "secure-payment-confirmation" payment handler, return a "NotAllowedError" DOMException.

    Tests

    Note: This guards against websites trying to access the extended powers of SPC without going through the transaction UX.

  2. During [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors):

    1. Skip step 6.1, which compares options.rpId to effectiveDomain

    Tests

    Note: This enables cross-domain authentication ceremonies; see § 1.1.3 Merchant control of authentication.

    1. In step 9, instead of creating a CollectedClientData, instead create a CollectedClientPaymentData with:

      1. type set to "payment.get"

      2. payment set to a new CollectedClientAdditionalPaymentData whose fields are:

        rp

        extension_inputs["rp"]

        topOrigin

        extension_inputs["topOrigin"]

        payeeName

        extension_inputs["payeeName"] if it is present, otherwise omitted.

        payeeOrigin

        extension_inputs["payeeOrigin"] if it is present, otherwise omitted.

        total

        extension_inputs["total"]

        instrument

        extension_inputs["instrument"]

      3. All other fields set as per the original step 9.

      Tests
Client extension output

None

Authenticator extension processing

None

5.1. CollectedClientPaymentData Dictionary

dictionary CollectedClientPaymentData : CollectedClientData {
    required CollectedClientAdditionalPaymentData payment;
};

The CollectedClientPaymentData dictionary inherits from CollectedClientData. It contains the following additional field:

payment member, of type CollectedClientAdditionalPaymentData

The additional payment information to sign.

5.2. CollectedClientAdditionalPaymentData Dictionary

dictionary CollectedClientAdditionalPaymentData {
    required USVString rp;
    required USVString topOrigin;
    DOMString payeeName;
    USVString payeeOrigin;
    required PaymentCurrencyAmount total;
    required PaymentCredentialInstrument instrument;
};

The CollectedClientAdditionalPaymentData dictionary contains the following fields:

rp member, of type USVString

The id of the Relying Party that created the credential.

topOrigin member, of type USVString

The origin of the top level context that requested to sign the transaction details.

payeeName member, of type DOMString

The name of the payee, if present, that was displayed to the user.

payeeOrigin member, of type USVString

The origin of the payee, if present, that was displayed to the user.

total member, of type PaymentCurrencyAmount

The PaymentCurrencyAmount of the [payment-request] total field.

instrument member, of type PaymentCredentialInstrument

The instrument information that was displayed to the user.

Note that there is no paymentRequestOrigin field in CollectedClientAdditionalPaymentData, because the origin of the calling frame is already included in CollectedClientData of [webauthn-3].

6. Common Data Structures

The following data structures are shared between registration and authentication.

6.1. PaymentCredentialInstrument Dictionary

dictionary PaymentCredentialInstrument {
    required DOMString displayName;
    required USVString icon;
    boolean iconMustBeShown = true;
};

The PaymentCredentialInstrument dictionary contains the information to be displayed to the user and signed together with the transaction details. It contains the following members:

displayName member, of type DOMString

The name of the payment instrument to be displayed to the user.

NOTE: For discussion about internationalization of the displayName, see issue 93.

icon member, of type USVString

The URL of the icon of the payment instrument.

NOTE: The icon URL may either identify an image on an internet-accessible server (e.g., https://bank.com/card.png), or directly encode the icon data via a Data URL [RFC2397]. Between the two types of URLs, Data URLs offer several benefits to the Relying Party. They can improve reliability (e.g., in the case that the icon hosting server may be unavailable). They can also enhance validation because the Relying Party has cryptographic evidence of what the browser displayed to the user: the icon URL is signed as part of the CollectedClientAdditionalPaymentData structure.

NOTE: See related accessibility considerations.

iconMustBeShown member, of type boolean, defaulting to true

Indicates whether the specified icon must be successfully fetched and shown for the request to succeed.

7. Permissions Policy integration

This specification uses the "payment" policy-identifier string from [payment-request] to control access to both registration and authentication. This extends the WebAuthn Permission Policy.

Tests

Note: Algorithms specified in [CREDENTIAL-MANAGEMENT-1] perform the actual permissions policy evaluation. This is because such policy evaluation needs to occur when there is access to the current settings object. The [[Create]](origin, options, sameOriginWithAncestors) and [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) internal methods do not have such access since they are invoked in parallel (by algorithms specified in [CREDENTIAL-MANAGEMENT-1]).

8. SPC Relying Party Operations

8.1. Verifying an Authentication Assertion

In order to perform an authentication ceremony for Secure Payment Confirmation, the Relying Party MUST proceed as follows:

  1. Let credential be a PublicKeyCredential returned from a successful invocation of the Secure Payment Confirmation payment handler by the SPC caller.

    Note: As SPC is designed to enable merchant control of authentication, the entity that invokes SPC may not be the Relying Party. This first step presumes that the SPC caller has returned a credential obtained via SPC to the Relying Party.

  2. Perform steps 3-21 as specified in WebAuthn, with the following changes:

    1. In step 5, verify that credential.id identifies one of the public key credentials provided to the SPC caller by the Relying Party.

    2. In step 11, verify that the value of C["type"] is the string payment.get.

    3. In step 12, verify that the value of C["challenge"] equals the base64url encoding of the challenge provided to the SPC caller by the Relying Party.

    4. In step 13, verify that the value of C["origin"] matches the origin that the Relying Party expects SPC to have been called from.

    5. After step 13, insert the following steps:

      • Verify that the value of C["payment"]["rp"] matches the Relying Party's origin.

      • Verify that the value of C["payment"]["topOrigin"] matches the top-level origin that the Relying Party expects.

      • Verify that the value of C["payment"]["payeeName"] matches the name of the payee that should have been displayed to the user, if any.

      • Verify that the value of C["payment"]["payeeOrigin"] matches the origin of the payee that should have been displayed to the user, if any.

      • Verify that the value of C["payment"]["total"] matches the transaction amount that should have been displayed to the user.

      • Verify that the value of C["payment"]["instrument"] matches the payment instrument details that should have been displayed to the user.

9. User Agent Automation

For the purposes of user agent automation and website testing, this document defines the below [WebDriver] extension commands. Interested parties should also consult the equivalent automation section in [webauthn-3].

9.1. Set SPC Transaction Mode

The Set SPC Transaction Mode WebDriver extension command instructs the user agent to place Secure Payment Confirmation into a mode where it will automatically simulate a user either accepting or rejecting the transaction confirmation UX.

The current transaction automation mode tracks what TransactionAutomationMode is currently active for SPC. It defaults to "none".

enum TransactionAutomationMode {
  "none",
  "autoaccept",
  "autoreject"
};
HTTP Method URI Template
POST /session/{session id}/secure-payment-confirmation/set-mode

The remote end steps are:

  1. If parameters is not a JSON Object, return a WebDriver error with WebDriver error code invalid argument.

  2. Let mode be the result of getting a property named "mode" from parameters.

  3. If mode is undefined or is not a member of TransactionAutomationMode, return a WebDriver error with WebDriver error code invalid argument.

  4. Set the current transaction automation mode to mode.

  5. Return success with data null.

10. Security Considerations

As this specification builds on top of WebAuthn, the WebAuthn Security Considerations are applicable. The below subsections comprise the current Secure Payment Confirmation-specific security considerations, where this specification diverges from WebAuthn.

10.1. Cross-origin authentication ceremony

A significant departure that Secure Payment Confirmation makes from WebAuthn is in allowing a third-party to initiate an authentication ceremony using credentials for a different Relying Party, and returning the assertion to the third party. This feature can expose Relying Parties to both login and payment attacks, which are discussed here.

10.1.1. Login Attack

As credentials created for Secure Payment Confirmation are valid WebAuthn credentials, it is possible that a Relying Party may wish to use the same credential for a given user for both login and payment. This allows a potential attack on the Relying Party’s login system, if they do not carefully verify the assertion they receive.

The attack is as follows:

  1. The user visits attacker.com, which is or pretends to be a merchant site.

  2. attacker.com obtains credentials for the user from relyingparty.com, either legitimately or by stealing them from relyingparty.com or another party with whom relyingparty.com had shared the credentials.

  3. attacker.com initiates SPC authentication, and the user agrees to the transaction (which may or may not be legitimate).

  4. attacker.com takes the payment assertion that they received from the API call, and sends it to the login endpoint for relyingparty.com, e.g. by sending a POST to https://relyingparty.com/login.

  5. relyingparty.com is employing faulty assertion validation code, which checks the signature but fails to validate the necessary fields (see below), and believes the login attempt to be legitimate.

  6. relyingparty.com returns e.g. a login cookie to attacker.com. The user’s account at relyingparty.com has now been compromised.

Relying Parties can guard against this attack in two ways.

Firstly, a Relying Party must always follow the correct assertion validation steps either for WebAuthn login or SPC payment as appropriate. In particular, the following fields can all be used to detect an inappropriate use of a credential:

Secondly, a Relying Party can consider keeping their payment and login credentials separate. If doing this, the Relying Party should only register credentials for Secure Payment Confirmation on a subdomain (e.g. https//payment.relyingparty.com), and should keep payment credentials and login credentials separate in their database.

NOTE: As currently written, the Secure Payment Confirmation specification allows any WebAuthn credential to be used in an SPC authentication. However this is not true in implementations today, which only allow credentials created with the payment extension specified to participate in SPC authentication, and the specification may be updated to reflect that in the future.

In both implementation and specification today, a credential created with the payment can be used for login, if the Relying Party wishes. This is not expected to change.

10.1.2. Payment Attack

A Secure Payment Confirmation assertion is essentially useless unless it is part of an ongoing online transaction.

A variety of mechanisms protect against an attack where a malicious third-party, instead of attempting to hijack a user account, initiates an unauthorized payment using Secure Payment Confirmation credentials (obtained either legitimately or otherwise):

10.2. Merchant-supplied authentication data

The bank can and should protect against spoofing by verifying the authentication assertion they receive to ensure it aligns with the transaction details provided by the merchant.

That is because a consequence of this specification’s third-party authentication ceremony is that even in a valid transaction (i.e. one that the Relying Party is expecting), a third-party provides the transaction details that are shown to the user:

This could lead to a spoofing attack, in which a merchant presents incorrect data to the user. For example, the merchant could tell the bank (in the backend) that it is initiating a purchase of $100, but then pass $1 to the SPC API (and thus show the user a $1 transaction to verify). Or the merchant could provide the correct transaction details but pass Secure Payment Confirmation credentials that don’t match what the Relying Party expects.

Secure Payment Confirmation actually makes defeating this kind of attack easier than it currently is on the web. In online payments today, the bank has to trust that the merchant showed the user the correct amount in their checkout flow (and any fraud discoveries are post-payment, when the user checks their account statement).

11. Privacy Considerations

As this specification builds on top of WebAuthn, the WebAuthn Privacy Considerations are applicable. The below subsections comprise the current Secure Payment Confirmation-specific privacy considerations, where this specification diverges from WebAuthn.

11.1. Registration in a Cross-Origin iframe

Unlike WebAuthn, this specification allows the creation of credentials in a cross-origin iframe (as long as the appropriate Permission Policy is set on the iframe). That is, if site A embeds an iframe from site B, with the "payment" policy set, then site B may initiate a credential creation for site B within that iframe.

NOTE: Allowing credential creation in cross-origin iframes is currently under discussion in the WebAuthn Working Group, and thus may move from this specification to WebAuthn in the future.

Allowing credential creation in a cross-origin iframe presents a risk that an iframe may attempt to trick a user into registering a credential. That credential could then be used for tracking (see WebAuthn issue 1336). To mitigate such an attack, this specification requires that a call to navigator.credentials.create() inside a cross-origin iframe may only be invoked when the iframe has transient activation (e.g., via a click or press from the user).

NOTE: Requiring user activation for WebAuthn APIs in general is under discussion in the WebAuthn WG too; see issue #1293.

11.2. Probing for credential ids

As per WebAuthn’s section on Authentication Ceremony Privacy, implementors of Secure Payment Confirmation must make sure not to enable malicious callers (who now may not even be the Relying Party) to distinguish between these cases:

If the above cases are distinguishable, information is leaked by which a malicious Relying Party could identify the user by probing for which credentials are available.

Section § 4.1.6 Steps to check if a payment can be made gives normative steps to mitigate this risk.

11.3. Joining different payment instruments

If a Relying Party uses the same credentials for a given user across multiple payment instruments, this might allow a merchant to join information about payment instruments that might otherwise not be linked. That is, across two different transactions that a user U performs with payment instruments P1 and P2 (either on the same merchant M, or two colluding merchants M1 and M2), the merchant(s) may now be able to learn that P1 and P2 are for the same user.

For many current online payment flows this may not be a significant risk, as the user often provides sufficient information to do this joining anyway (e.g., name, email address, shipping address).

However, if payment methods that involve less identifying information (e.g., tokenization) become commonplace, it is important that ecosystem stakeholders take steps to preserve user privacy. For example:

NOTE: See issue #77 for discussion on possible spec changes related to this section.

11.4. Credential ID(s) as a tracking vector

Even for a single payment instrument, the credential ID(s) returned by the Relying Party could be used by a malicious entity as a tracking vector, as they are strong, cross-site identifiers. However in order to obtain them from the Relying Party, the merchant already needs an as-strong identifier to give to the Relying Party (e.g., the credit card number).

NOTE: See issue #77 for discussion on possible spec changes related to this section.

12. Accessibility Considerations

User agents render the icon and displayName together. Relying parties ensure the accessibility of the icon presentation by providing sufficient information via the displayName (e.g., if the icon represents a bank, by including the bank name in the displayName).

User Agents implementing this specification should follow both WebAuthn’s Accessibility Considerations and PaymentRequest’s Accessibility Considerations.

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Tests

Tests relating to the content of this specification may be documented in “Tests” blocks like this one. Any such block is non-normative.


Conformant Algorithms

Requirements phrased in the imperative as part of algorithms (such as "strip any leading space characters" or "return false and abort these steps") are to be interpreted with the meaning of the key word ("must", "should", "may", etc) used in introducing the algorithm.

Conformance requirements phrased as algorithms or specific steps can be implemented in any manner, so long as the end result is equivalent. In particular, the algorithms defined in this specification are intended to be easy to understand and are not intended to be performant. Implementers are encouraged to optimize.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[CREDENTIAL-MANAGEMENT-1]
Mike West. Credential Management Level 1. 17 January 2019. WD. URL: https://www.w3.org/TR/credential-management-1/
[FileAPI]
Marijn Kruisselbrink; Arun Ranganathan. File API. 4 June 2021. WD. URL: https://www.w3.org/TR/FileAPI/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[IMAGE-RESOURCE]
Aaron Gustafson; Rayan Kanso; Marcos Caceres. Image Resource. 4 June 2021. WD. URL: https://www.w3.org/TR/image-resource/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[PAYMENT-METHOD-ID]
Marcos Caceres. Payment Method Identifiers. 30 September 2021. PR. URL: https://www.w3.org/TR/payment-method-id/
[PAYMENT-REQUEST]
Marcos Caceres; Rouslan Solomakhin; Ian Jacobs. Payment Request API. 30 September 2021. PR. URL: https://www.w3.org/TR/payment-request/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[URL]
Anne van Kesteren. URL Standard. Living Standard. URL: https://url.spec.whatwg.org/
[WEBAUTHN-1]
Dirk Balfanz; et al. Web Authentication:An API for accessing Public Key Credentials Level 1. 4 March 2019. REC. URL: https://www.w3.org/TR/webauthn-1/
[WEBAUTHN-3]
Jeff Hodges; et al. Web Authentication: An API for accessing Public Key Credentials - Level 3. 27 April 2021. WD. URL: https://www.w3.org/TR/webauthn-3/
[WebDriver]
Simon Stewart; David Burns. WebDriver. 5 June 2018. REC. URL: https://www.w3.org/TR/webdriver1/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

Informative References

[ECMASCRIPT]
ECMAScript Language Specification. URL: https://tc39.es/ecma262/multipage/
[RFC2397]
L. Masinter. The "data" URL scheme. August 1998. Proposed Standard. URL: https://www.rfc-editor.org/rfc/rfc2397
[WEBAUTHN-CONDITIONAL-UI]
WebAuthn Conditional UI Proposal. URL: https://github.com/w3c/webauthn/issues/1545

IDL Index

dictionary SecurePaymentConfirmationRequest {
    required BufferSource challenge;
    required USVString rpId;
    required FrozenArray<BufferSource> credentialIds;
    required PaymentCredentialInstrument instrument;
    unsigned long timeout;
    DOMString payeeName;
    USVString payeeOrigin;
    AuthenticationExtensionsClientInputs extensions;
};

partial dictionary AuthenticationExtensionsClientInputs {
  AuthenticationExtensionsPaymentInputs payment;
};

dictionary AuthenticationExtensionsPaymentInputs {
  boolean isPayment;

  // Only used for authentication.
  USVString rp;
  USVString topOrigin;
  DOMString payeeName;
  USVString payeeOrigin;
  PaymentCurrencyAmount total;
  PaymentCredentialInstrument instrument;
};

dictionary CollectedClientPaymentData : CollectedClientData {
    required CollectedClientAdditionalPaymentData payment;
};

dictionary CollectedClientAdditionalPaymentData {
    required USVString rp;
    required USVString topOrigin;
    DOMString payeeName;
    USVString payeeOrigin;
    required PaymentCurrencyAmount total;
    required PaymentCredentialInstrument instrument;
};

dictionary PaymentCredentialInstrument {
    required DOMString displayName;
    required USVString icon;
    boolean iconMustBeShown = true;
};

enum TransactionAutomationMode {
  "none",
  "autoaccept",
  "autoreject"
};