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], whilst relaxing certain constraints to meet the needs of payment processing.
Similarly to [webauthn-3], this specification defines two related processes involving a user. The first is § 3 Enrollment, where a relationship is created between the user and the Relying Party. The second is § 4 Authentication, where a specific payment from the user to a recipient (possibly via an intermediary payment service provider) is authenticated for the Relying Party. An important concept in Secure Payment Confirmation is that with the permission of the Relying Party, the merchant or another entity may initiate an authentication ceremony on the Relying Party’s behalf.
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
The below use case scenarios illustrate the need for a payment-specific extension to [webauthn-3], rather than just asking web developers to build their own flows on-top of [webauthn-3] registration and authentication. 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. Verifying an authenticated payment
A user is performing an online transaction based on cryptographic authentication (i.e. either WebAuthn or this specification). The user completes the authentication and the issuing bank now wishes to verify the signed cryptogram that they received.
If the bank or merchant site is using WebAuthn, the payment-specific
information must be placed in the WebAuthn challenge
. This has several issues:
-
It is a misuse of the
challenge
field (which is intended to defeat replay attacks). -
There is no specification for this, so each issuing bank may come up with their own format for how payment information should be formatted and encoded in the challenge - which causes fragmentation and confusion.
-
Regulations may require that the user was shown the transaction amount. The
challenge
field cannot be used to verify that, as information included there has no behavior impact on WebAuthn.
Secure Payment Confirmation, on the other hand:
-
Uses the
challenge
field only to defeat replay attacks, as with normal WebAuthn. -
Provides a specified format for transaction data, which will e.g. allow generic verification code and test suites to be developed and shared as appropriate.
-
Guarantees that the user agent has presented the transaction information to the user, in a way that a malicious website (or maliciously introduced JavaScript code on a trusted website) cannot bypass.
-
The transaction information is included in the
CollectedClientData
dictionary, which is not influencable by JavaScript code.
NOTE: This does assume the issuing bank trusts the user agent, but that is already required in payment flows today.
-
1.1.2. Registration in a bank iframe
-
It is very common in payment flows on the web to open an iframe to a bank for ID&V (e.g. via an SMS OTP step-up flow). This is the highest traffic authenticated touchpoint that the bank has with the user, and is an ideal point for an enrollment flow.
-
It is expected that requiring out-of-flow enrollment (e.g. on a bank website, outside of a payment flow), would lead to far lower enrollment rates for SPC.
-
-
See https://github.com/w3c/webauthn/issues/1336#issuecomment-554170183
1.1.3. Authentication on a merchant website
-
A prime concern for merchants on the web is to prevent user drop-off during authentication. Reducing friction as much as possible is key, so instead of opening a bank iframe (the relying party) and letting them do SPC authentication, merchants strongly want to perform the auth themselves on behalf of the bank.
-
This also means the banks don’t need to build their own SPC front-end flows for authentication; the merchants (or the payment service processors who they use) can do so.
-
Of course its ultimately up to the relying party to allow this usage, so they don’t have to if they don’t like it. They could use SPC in a normal challenge flow inside their own iframe if they wanted.
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. Enrollment
This is the first-time flow, in which a new credential is created and stored by the issuing bank.
-
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). -
The merchant communicates with the issuing bank of the payment instrument over a backchannel. The issuing bank requests verification of the user, and provides a bank-owned URL for the merchant to open in an iframe.
-
The merchant opens an iframe to
bank.com
, with theallow
attribute set to "payment". -
In the iframe, the issuing bank confirms the user’s identity via a traditional means (e.g. SMS OTP). After confirming, they offer the user the ability to enroll in SPC authentication for future payments.
-
The user consents (e.g. by clicking an "Enroll" button in the bank UX), and the bank runs the below example code in the iframe.
-
The user goes through a WebAuthn registration flow. A new credential is created for the user and the public key is returned to the issuing bank who stores it in their server-side database associated with the user.
-
The verification completes; the bank iframe closes and the merchant finishes the checkout process for the user.
The sample code for enrolling the user 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: { // An id that the bank server can use to identify this user in future interactions. id: Uint8Array. from( window. atob( "MIIBkzCCATigAwIBAjCCAZMwggE4oAMCAQIwggGTMII=" ), c=> c. charCodeAt( 0 )), name: "jane.doe@example.com" , displayName: "Jane Doe" , }, // This Relying Party will accept either an ES256 or RS256 credential, but // prefers an ES256 credential. pubKeyCredParams: [ { type: "public-key" , alg: - 7 // "ES256" }, { type: "public-key" , alg: - 257 // "RS256" } ], // This Relying Party requires user verification. authenticatorSelection: { userVerification: "required" }, timeout: 360000 , // 6 minutes // Indicate that this is an SPC credential. This is currently required to // allow credential creation in an iframe, and so that the browser knows this // credential relates to SPC. // // It is expected that 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
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.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).Note: This may or may not be the merchant website the user was originally on when they registered this device; it is irrelevant here.
-
The merchant communicates with the issuing bank of the payment instrument over a backchannel. The issuing bank requests verification of the user, but notes that it accepts SPC and provides a list of known credential IDs for this payment instrument.
-
The merchant runs the below example code.
-
The user accepts the transaction in the SPC UX, and performs a subsequent WebAuthn authentication ceremony. The signed cryptogram is returned to the merchant.
-
The merchant sends the signed cryptogram to the issuing bank via the backchannel. The issuing bank verifies the cryptogram, and knows that the user is both valid and 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, // 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" , }, timeout: 360000 , // 6 minutes }], { total: { label: "Total" , amount: { currency: "USD" , value: "5.00" , }, }, }); 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. Dependencies
This specification relies on several other underlying specifications, listed below and in Terms defined by reference.
- WebAuthn Conditional UI
-
Secure Payment Confirmation only shows the transaction UX if one of the passed credentials is valid for the current device, without requiring a user interaction. This concept is not currently part of [webauthn-3], but is on the roadmap for a future enhancement.
Until this is available, user agents can consider either storing a local cache of WebAuthn credentials created for this device, or always showing the transaction UX even if the user may be unable to complete the authentication.
TODO: Bikeshed the name.
3. Enrollment
To enroll a user for Secure Payment Confirmation, relying parties should call navigator.credentials.create()
, with the payment
WebAuthn Extension specified.
Note: We currently require an extension in order to allow credential creation in an iframe and to allow the browser to cache SPC credentials in the absence of WebAuthn Conditional UI. Future versions of this specification may remove the requirement for the extension.
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 transaction.
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 Authentication on a merchant website.
4.1. Payment Method: Secure Payment Confirmation
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 this specification is "secure-payment-confirmation".
4.1.2. SecurePaymentConfirmationRequest
Dictionary
dictionary SecurePaymentConfirmationRequest {required BufferSource challenge ;required FrozenArray <BufferSource >credentialIds ;required PaymentCredentialInstrument instrument ;unsigned long timeout ;AuthenticationExtensionsClientInputs extensions ; };
The SecurePaymentConfirmationRequest
dictionary contains the following
members:
challenge
member, of type BufferSource-
A random one-time challenge that the relying party generates on the server side to prevent replay attacks.
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 enrollment 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.
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.3. 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
request
, are:
-
If
request.credentialIds
is empty, returnfalse
. -
If
request.instrument.displayName
is empty, returnfalse
. -
Download the image specified in
request.instrument.icon
. If this fails, returnfalse
.Note: Performing this step here mitigates a privacy leak. TODO: Document + link-to privacy section.
-
Optionally, the user agent may elect to return
false
.Note: This covers the current Chrome behavior of checking whether the passed credentials match those on the system, and early-exit if so. This is a potential privacy concern, and may be removed.
-
Return
true
.
4.1.4. Displaying a transaction confirmation UX
4.1.5. Steps to respond to a payment request
The steps to respond to a payment request for this payment method, for an
input SecurePaymentConfirmationRequest
request
, are:
-
Create a
AuthenticationExtensionsPaymentInputs
dictionary, payment, with:-
isPayment
set totrue
. -
rpOrigin
set to TODO.TODO: We don’t have the rp origin here; maybe this should just go in the processing steps. -
topOrigin
set to the origin of the top-level frame.TODO: This is part of the PaymentRequestEvent, but it’s unclear how to access that in an inbuilt PaymentHandler. -
total
set to thetotal
from the PaymentRequest.TODO: This is part of the PaymentRequestEvent, but it’s unclear how to access that in an inbuilt PaymentHandler. -
instrument
set torequest.instrument
.
-
-
Create a
PublicKeyCredentialRequestOptions
,publicKeyOpts
, with:-
challenge
set torequest.challenge
. -
timeout
set torequest.timeout
. -
userVerification
set torequired
. -
extensions
set to aAuthenticationExtensionsClientInputs
dictionary whosepayment
member is set to payment, and whose other members are set fromrequest.extensions
.
-
-
For each id in
request.credentialIds
:-
Create a
PublicKeyCredentialDescriptor
, descriptor, with:-
type
set topublic-key
-
id
set to id -
transports
set to a sequence of length 1 whose only member isinternal
.
-
-
Push descriptor onto
publicKeyOpts.allowCredentials
.
-
-
Let outputCredential be the result of calling
navigator.credentials.get({publicKey})
.Note: This triggers [webauthn-3]'s Get behavior
-
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.
- Extension identifier
-
payment
- Operation applicability
- Client extension input
-
partial dictionary AuthenticationExtensionsClientInputs {AuthenticationExtensionsPaymentInputs
; };payment dictionary
{AuthenticationExtensionsPaymentInputs boolean isPayment ; // Only used for authentication.USVString rpOrigin ;USVString topOrigin ;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. rpOrigin
member, of type USVString-
The Relying Party origin of the credential(s) being used. Only valid at authentication time.
topOrigin
member, of type USVString-
The origin of the top-level frame. Only valid at authentication time.
total
member, of type PaymentCurrencyAmount-
The total amount the user is paying to the payee. Only valid at authentication time.
instrument
member, of type PaymentCredentialInstrument-
The instrument details to be displayed to the user. Only valid at authentication time.
- 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:
-
Remove the check for sameOriginWithAncestors in step 2.
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). We could additionally require and consume a transient activation here, if we felt the permission policy is not sufficient.
-
In step 13, set the
type
to "payment.create
".TODO: Can we and/or should we rely ongetClientExtensionResults()
instead?
-
- Client extension processing (authentication)
-
When making an assertion:
-
If not in a "secure-payment-confirmation" payment handler, return a "
NotAllowedError
"DOMException
.Note: This guards against websites trying to access the extended powers of SPC without going through the browser UX.
-
During
[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)
:-
Skip step 6.1, which compares options.rpId to effectiveDomain
Note: This enables cross-domain authentication ceremonies; see § 1.1.3 Authentication on a merchant website.
-
In step 9, instead of creating a
CollectedClientData
, instead create aCollectedClientPaymentData
with:-
type
set to "payment.get"
-
payment
set to a newCollectedClientAdditionalPaymentData
with:-
rpOrigin
set torpOrigin
. -
topOrigin
set totopOrigin
. -
total
set tototal
. -
instrument
setinstrument
.
-
-
All other members set as per the original step 9.
-
-
-
- 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 ;required PaymentCurrencyAmount total ;required PaymentCredentialInstrument instrument ; };
The CollectedClientAdditionalPaymentData
dictionary contains the following
fields:
rp
member, of type USVString-
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. Typically this would be called a merchant.
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 enrollment and authentication.
6.1. PaymentCredentialInstrument
Dictionary
dictionary PaymentCredentialInstrument {required DOMString displayName ;required USVString icon ; };
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.
icon
member, of type USVString-
The URL of the icon of the payment instrument.
7. Permissions Policy integration
This specification uses the "payment" policy-identifier string from [payment-request] to control access to both enrollment and authentication. This extends the WebAuthn Permission Policy.
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. Security Considerations
Note: This section is still very much a draft.
Main considerations (on top of WebAuthn itself):
-
A successful payment backed by Secure Payment Confirmation will always require two things: the user to provide consent and the Relying Party to accept the signed cryptogram. As such, even if a malicious party obtains the credential list from the Relying Party, it is unlikely for fraud to occur:
-
First, the user must provide their consent (and verification) to the website - with either browser and/or authenticator UX that shows the transaction details.
-
Assuming the user does consent and verify the transaction, the Relying Party must still be expecting the payment, otherwise the cryptogram is useless.
-
-
The same argument is used against the concern that a third party (the merchant) provides many of the input values for SPC authentication (e.g. the card icon, name, etc). Whilst this may be confusing to the user if the merchant is malicious, the cryptogram is ultimately useless unless the Relying Party accepts it - and they are able to cryptographically verify what was shown to the user.
-
Allowing [[Create]] in an iframe; see https://github.com/w3c/webauthn/issues/1336 from WebAuthn.
-
Currently this is mostly amerliorated by the fact that we show a payments-focused browser UX on SPC enrollment.
-
If we got rid of that UX (under consideration), then we could require a user gesture for calling it. This would be fine for the bank iframe enrollment case.
-
9. Privacy Considerations
Note: This section is still very much a draft.
SPC primarily points to WebAuthn for its privacy model, but it does relax that model in significant ways that should be considered.
-
Probing for credential ids
-
Currently SPC exits immediately (on Chrome) when no passed credential matches the current device. This could be used to probe for whether the current device matches an existing known credential. This does show a very clear payments UX when a match is found (though that may be too late).
Note: The instant-exit may be removed in the future.
-
-
Identifying user across different payment instruments.
-
If the relying party uses the same credentials for a given user across multiple payment instruments, this could expose that linkage to the merchant (as they could track what sets of credentials they see for each credit card they ask about on the backend).
-
-
Credential ID(s) as a tracking vector
-
The credential ID could be used as a tracking vector, but to obtain it from the Relying Party the merchant already needs an as-strong identifier to give to the Relying Party (e.g. the credit card number).
-
10. Accessibility Considerations
User Agents implementing this specification should follow both WebAuthn’s Accessibility Considerations and PaymentRequest’s Accessibility Considerations.