w3c / secure-payment-confirmation Goto Github PK
View Code? Open in Web Editor NEWSecure Payment Confirmation (SPC)
Home Page: https://w3c.github.io/secure-payment-confirmation/
License: Other
Secure Payment Confirmation (SPC)
Home Page: https://w3c.github.io/secure-payment-confirmation/
License: Other
For privacy, a user may wish to enroll a new WebAuthn credential with every instrument so that the RP cannot correlate instruments. The user should be able to request this behavior in SPC.
Is there a Chrome/Edge/Firefox build that I can play with to test that out yet?
The initial proposal assumed that Payment Request API (PR API) should be used to trigger the SPC flow. The client code would use PR API providing the payment method as predefined value e.g. "secure-payment-confirmation" and the necessary parameters such as total amount, cryptographic nonce, fallback URL, and so on.
Alternatively, the same SPC flow could be triggered simply by invoking the Credential Management API CredentialsContianer.get() method as it happens for other types of credentials. Example SPRC request:
navigator.credentials. get({
spc:{
credentialIds: 'array of credential identifiers',
amount: 'amount w/currency',
nonce: 'server-side generated cryptographic nonce',
fallbackUrl: 'URL of the fallback page',
timeout: 'timeout for the request'
})
Similarly, the SPCCredential or PaymentCredential would extend the Credential interface to be consistent with the Credential Management API.
Are there any clear benefits of using Payment Request API instead of Credential Management API?
Arguments for 1 credential for many instruments:
Arguments for 1 credential for 1 instrument:
Next step is to try to enumerate attack vectors for sharing credential.
Hi all,
Lawrence Cheng and I were chatting today and he raised an issue I had not yet thought about: unenrollment of SPC credentials. It seems to me there are multiple topics here:
Are the second two necessary? If so, from an API perspective is anything needed? Or is this just "good practice" documentation?
I have not yet looked into what FIDO says about unenrollment. That could be a good starting point.
Ian
This issue replaces #22.
Issue 22 was one solution to the problem: the instrument ID should not be usable to track the user. Requiring the instrument ID to not leave the browser may overconstrain us.
Here is one proposal: When the relying party registers the payment credential with the browser, they register some significant number of instrument IDs (e.g., 1024), all of which map to the actual instrument on the RP's back end. When the user selects a payment credential, the browser returns a random instrument ID to the merchant. That would likely make tracking more difficult. It would not likely require a lot of storage in the browser (since users won't have a large number of cards). It would be easy for the browser computationally, and not involve any network connections during the transaction.
I had also thought about hashing the instrument ID with the transaction ID to introduce dynamic data, but that would likely create more computation burden for the relying party.
Ian
We’ve heard a lot about ‘frictionless authentication’ at TPAC 2020 regarding SPC. It’s considered an essential requirement for merchant adoption in certain markets, due to the historic pain experienced with issuer challenges. It's a key benefit promised for 3D Secure 2.
This post is an suggestion on how we could utilize the SPC public-key-credential to payment-card linking mechanism to enable frictionless authentication. The idea is inspired by the Credential API picture in the presentation given at the TPAC WPWG sessions (slide 14, thanks @danyao).
SPC was implemented by adding a new credential type (PaymentCredential
) to the Credential Management API. This new PaymentCredential
type includes a list of other trusted public-key credentials; which today consist of a set of WebAuthn Keys (called PublicKeyCredential
in the Credential Management API).
What if we supported an additional type of credential that can be used frictionlessly. Ideally this would also be a public-private keypair, similar to Webautn’s PublicKeyCredential. This credential would have the following attributes:
This attribute would then be able to show a certain browser presence during a transaction.
This functionality actually already exists in WebCrypto API. The object created is called CryptoKey
.
In CredentialManagement, we could call this a CryptoKeyCredential
. The CredentialManagement API would bring the ability to get user-consent when this is created. And it would then be stored and retrievable in a similar way to other Credentials.
Our PaymentCredential
would then be able to link into both PublicKeyCredentials
and CryptoKeyCredentials
(via existingCredential
).
So, if this was in place and an issuer wanted to enable frictionless flows it would just need to do the following:
CryptoKeyCredential
on a trusted browser (getting user consent that they trust this browser)CryptoKeyCredential
to a PaymentCredential
, potentially also with a set of PublicKeyCredentials
that’s also trusted by this userCryptoKeyCredentials
in the list returned to the merchant.CryptoKeyCredentials
returned are present on the browser, and if they are, they could be used as signing proof of the transaction. If none are present it would try the WebAuthnCredentials
, and failing that fallback to the fallback URL.If I understand the SPC API and implementation correctly, this would require virtually no change to the SPC interface. Just the ability to store a new credential type in the browser. And in return, we would be able to offer frictionless SPC consent.
Would be great to explain and explore this further in future WPWG session.
Hi all,
In the SRC flow, a persistent user identifier is used to fetch card metadata for display and selection by the user. As browser behavior changes around 3p cookies, implementations are likely suffer.
I was chatting with @adrianhopebailie today about how we might address this (e.g., in a credential management API sort of way). We convered on a particular user experience that we liked and thought we could achieve this by building on top of the (still-being-designed) SPC payment credential data. Imagine for a moment that a payment credential can optionally include an identifier that means "This payment credential is part of a user profile that the RP identifies with this identifier."
We are very conscious of tracking, so this idea endeavors to only provide information with user consent.
Here's how it might work (all from a 3p context)
Thus, the identifier is shared with the 3p after two user gestures:
The identifier can be used to fetch more payment instruments associated with that user identity, and those can be displayed to the user for selection, followed by SPC authentication.
Thus we have parallel behaviors:
For identity: ask the browser to have the user pick an identity from stored payment credentials, otherwise silently return null.
For authentication: ask the browser to authenticate the user for a list of payment credential identifiers, otherwise silently return null.
From the samples it appears that there is just a payment method and no payment instruments.
If this is correct, the universality of this system will be unnecessary limited.
Discussion for the coming SPC specification.
Integrating SPC with Open Banking is a complex matter. The most intricate part of the puzzle is where exactly a PISP fits into such a schema. Dirk Balfanz's presentation highlights the problem:
https://www.w3.org/2020/02/3p-creds-20200219.pdf#page=5
In this slide the User is supposed to authorize a payment to his/her PISP. This introduces several obstacles:
Chris Wood's recent F2F presentation shows the consequences:
https://docs.google.com/document/d/1qjBPa6l0EM9A3sLl9neccq_8UPHe90jXTGXqcbge2vQ
Note that this example concentrates on Confidential Clients, but it could equally apply to Public Clients with the right tweaks.
The initial consent will incorporate the SPC Credential ID as an additional property, named SPCCredentialId:_
{
"Data": {
"ReadRefundAccount": "Yes",
"Initiation": {
"InstructionIdentification": "ACME412",
"EndToEndIdentification": "FRESCO.21302.GFX.20",
"InstructedAmount": {
"Amount": "99.99",
"Currency": "GBP"
},
"CreditorAccount": {
"SchemeName": "UK.OBIE.SortCodeAccountNumber",
"Identification": "08080021325698",
"Name": "ACME Inc"
},
"RemittanceInformation": {
"Reference": "FRESCO-101",
"Unstructured": "Internal ops code 5120101"
}
},
"SCASupportData": {
"SPCCredentialId": "8bbea266-921b-4015-9bc9-a5017b310b54"
}
},
"Risk": {
"PaymentContextCode": "EcommerceServices",
"MerchantCategoryCode": "5967",
"MerchantCustomerIdentification": "053598653254"
}
}
On success the bank will return an identifier for the newly created Consent entity in the property ConsentId:
{
"Data": {
"ConsentId": "f73ff601-e969-43d1-961e-fdfcbd1b170d",
"Status": "AwaitingAuthorisation",
"CreationDateTime": "2017-06-05T15:15:13+00:00",
"StatusUpdateDateTime": "2017-06-05T15:15:13+00:00",
"ReadRefundAccount": "Yes",
"Initiation": {
"InstructionIdentification": "ACME412",
"EndToEndIdentification": "FRESCO.21302.GFX.20",
"InstructedAmount": {
"Amount": "99.99",
"Currency": "GBP"
},
"CreditorAccount": {
"SchemeName": "UK.OBIE.SortCodeAccountNumber",
"Identification": "08080021325698",
"Name": "ACME Inc"
},
"RemittanceInformation": {
"Reference": "FRESCO-101",
"Unstructured": "Internal ops code 5120101"
}
},
"SCASupportData": {
"SPCCredentialId": "8bbea266-921b-4015-9bc9-a5017b310b54"
}
},
"Risk": {
"PaymentContextCode": "EcommerceServices",
"MerchantCategoryCode": "5967",
"MerchantCustomerIdentification": "053598653254"
},
"Links": {
"Self": "https://api.alphabank.com/open-banking/v3.1/pisp/domestic-payment-consents/58923"
},
"Meta": {}
}
The Relying Party will use this value to populate the challenge property of the PaymentRequest call. The following snippet uses the existing example from the SPC proposal, amended to include the challenge property:
const securePaymentConfirmationRequest = {
action: 'authenticate',
challenge: 'f73ff601-e969-43d1-961e-fdfcbd1b170d',
credentialIds: [Uint8Array.from(atob('8bbea266-921b-4015-9bc9-a5017b310b54'), c => c.charCodeAt(0))],
networkData,
timeout,
fallbackUrl: "https://fallback.example/url"
};
const request = new PaymentRequest([{
supportedMethods: 'secure-payment-confirmation',
data: securePaymentConfirmationRequest
}],
{
total: {
label: 'total',
amount: {
currency: 'GBP',
value: '99.99'
}
}
});
const response = await request.show();
await response.complete('success');
Once the request is made and an Authentication Assertion successfully elicited by the Payment Handler it is sent to the bank using an authentication flow supported under FAPI
ALTERNATIVE
By putting the PISP in the same position as a payment gateway, none of the peculiarities of Open Banking payments need to trickle down to the User layer (and SPC), giving a simpler solution and as well as a better UX. The impact on Open Banking APIs would be marginal; the big job is the unavoidable Access Server/FIDO which in all variants becomes a special purpose system with its own API.
Raised in discussion with @Goosth:
What happens? Are there timeout parameters?
As a counter-proposal to #15 here is a proposal that would require no Payment Handlers (i.e. they could potentially be deprecated entirely) but would require that instruments are stored by RPs in the browser using an ID that is both "routable" by a merchant and opaque enough that it is not sensitive to share.
This is a minor modification to the existing Secure Payment Confirmation (SPC) proposal.
RPs (issuers, wallets etc) will register payment instruments in a users browser during an enrolment flow which involves calling the WebAuthn create
API to create a new PaymentCredential
.
A PaymentCredential
is a new type of credential which MAY be linked to a PublicKeyCredential
(which may be implicitly created when registering the PaymentCredential
). It contains payment instrument meta-data (i.e. a label and icon) and is mapped to one or more payment methods.
It will be possible to create a new PaymentCredential
linked to an existing PublicKeyCredential
. In this case the browser will invoke the authenticator's authentication flow using the details of the existing credential (as opposed to the registration flow which creates a new credential on the authenticator).
It will be possible to create a PaymentCredential
that is not linked to a PublicKeyCredential
. This instrument will appear in the browser-rendered instrument selector when appropriate but can't be used to invoke Secure Payment Confirmation. If a user selects one of these instruments then the merchant can only invoke the fallback authN flow by passing in an RP provided URL to render.
Whenever an RP registers an instrument there will be a user interaction whereby the user confirms the registration and their intent to use this for payments in the future. If the credential is linked to a PublicKeyCredential
this will also involve a user interaction with their authenticator as with existing WebAuthn registration and authentication flows.
The installed PaymentCredentials
serve multiple functions:
[SecureContext, Exposed=Window]
interface PaymentCredential : Credential {
};
partial dictionary CredentialCreationOptions {
PaymentCredentialCreationOptions securePayment;
};
dictionary PaymentCredentialCreationOptions {
// |instrumentId| is a caller provided ID for the payment instrument
// to which the new PaymentCredential should be bound. It should be
// an opaque string generated using a payment network specific algorithm
// that allows the network to identify the issuer of the instrument
// and the issuer to identify the account associated with this
// instrument. (e.g. form-preserving card token or URL)
required DOMString instrumentId;
required DOMString displayName;
required USVString icon;
required sequence<USVString> paymentMethods;
PaymentCredentialPublicKeyOptions createPublicKeyCredentialOptions
PublicKeyCredentialDescriptor existingPublicKeyCredential;
};
// Either |createPublicKeyCredentialOptions| or |existingPublicKeyCredential|
// or both must be provided.
// If both are provided then the browser should create the credential if
// the existing credential doesn't exist.
dictionary PaymentCredentialPublicKeyCreationOptions {
required PublicKeyCredentialRpEntity rp;
required BufferSource challenge;
required sequence<PublicKeyCredentialParameters> pubKeyCredParams;
unsigned long timeout;
// PublicKeyCredentialCreationOption attributes that are intentionally omitted:
// user: For a PaymentCredential, |instrument| is analogous to |user|.
// excludeCredentials: No payment use case has been proposed for this field.
// attestation: Authenticator attestation is considered an anti-pattern
// for adoption so will not be supported.
// extensions: No payment use case has been proposed for this field.
};
Example usage:
const securePaymentConfirmationCredentialCreationOptions = {
instrumentId: "Q1J4AwSWD4Dx6q1DTo0MB21XDAV76",
displayName: 'Mastercard····4444',
icon: 'icon.png',
paymentMethods: ["https://mastercard.com/pay", "https://clicktopay.com"]
existingCredential: {
type: "public-key",
id: Uint8Array.from(credentialId, c => c.charCodeAt(0))
},
publicKeyCreationOptions: {
challenge,
rp,
pubKeyCredParams,
timeout
}
};
// Bind new credential to |credentialId|, or create a new credential
// if |credentialId| doesn't exist.
const credential = await navigator.credentials.create({
securePayment: securePaymentCredentialCreationOptions
});
When a user visits a merchant there are two possible flows before Secure Payment Confirmation (or the fallback flow) is invoked.
canMakePayment
and the browser returns a "truthy" response if one of the provided instrument identifiers identifies an installed PaymentCredential
that supports one of the merchant provided payment methods. If there is an instrument linked to a PublicKeyCredential
then the response also indicates that Secure Payment Confirmation is available - i.e. the response has a property canDoSecurePaymentConfirmation
equal to true
.show()
. If the merchant provided multiple instrument identifiers and more than one PaymentCredential
is identified by one of the supplied identifiers (this shouldn't happen unless RPs have messed up) then the browser presents the user with an instrument selection list populated with all installed PaymentCredentials
that are identified by provided instrument identifiers. However, the browser will prefer to use a PaymentCredential
that is linked to a PublicKeyCredential
so that it can offer Secure Payment Confirmation. Therefor if only one PaymentCredential
that is identified by the supplied instrument identifiers is linked to a PublicKeyCredential
then that PaymentCredential
is auto-selected. If more than one PaymentCredential
linked to a PublicKeyCredential
is identified by the provided instrument identifiers then only those are listed for selection by the user.instrumentSelected
event emitted by the Payment Request object.canMakePayment
and the browser returns a "truthy" response if at least one of the installed PaymentCredentials
supports one of the provided payment methods. If there is an instrument linked to a PublicKeyCredential
then the response also indicates that Secure Payment Confirmation is available - i.e. the response has a property canDoSecurePaymentConfirmation
equal to true
show
and the browser presents the user with an instrument selection list populated with all installed PaymentCredential
s that support the payment methods the merchant supports.instrumentSelected
event emitted by the Payment Request object.At this point the User Provided Instrument and Instrument Selection Through The Browser flows converge.
The merchant passes the challenge and a fallback URL to the browser via a respondWith
method on the instrumentSelected
event.
If the merchant provided a challenge and the selected instrument is mapped to a PublicKeyCredential
the browser invokes Secure Payment Confirmation. The assertion generated is returned to the merchant in the PaymentResponse
.
If SPC fails or the merchant doesn't provide a challenge AND the merchant does provide a fallback URL then the browser shows a modal dialogue which renders the fallback URL and returns an instance of a Window
to the merchant in the PaymentResponse
. The fallback URL is only used if the origin is the same as the origin of the RP that enrolled the PaymentCredential
. The merchant should use postMessage
to communicate with the RP's window and can authenticate the user through some fallback mechanism such as SMS OTP.
Here's how a new simpler PR API could look with 3DS:
// Get instrument identifiers, challenge and fallback URL from network
const { instruments, challenge, fallbackUrl } = get3DSResponse(pan)
const request = new PaymentRequest({
supportedMethods: ["https://mastercard.com/pay", "http://clicktopay.com/"],
instruments // instruments = ["412938871562965", "41290981273767562"]
},{
currency: 'USD', value: '20.00'
})
const cmp = await request.canMakePayment()
if(cmp) {
request.addEventListener("instrumentSelected", e => {
e.respondWith({
challenge,
fallbackUrl
})
}
const {securePaymentConfirmation, fallbackWindow} = await request.show();
if(securePaymentConfirmation) {
do3DSStuff(securePaymentConfirmation)
return
}
if(fallbackWindow) {
fallbackWindow.addEventListener("message", e => {
//Handle messages from issuer window
})
}
} else {
//Legacy 3DS flow
}
Here's how a new simpler PR API could look WITH instrument selection:
const request = new PaymentRequest({
supportedMethods: ["https://mastercard.com/pay", "http://clicktopay.com/", "https://google.com/pay"],
},{
currency: 'USD', value: '20.00'
})
const cmp = await request.canMakePayment()
if(cmp) {
request.addEventListener("instrumentSelected", e => {
if(e.supportedMethods.includes("https://google.com/pay")) {
// Skip SPC and show Google Pay modal
e.respondWith({
fallbackUrl: "https://google.com/pay?instrument=" + e.instrumentId
})
return
}
const { challenge, fallbackUrl } = get3DSResponse(e.instrument)
e.respondWith({
challenge,
fallbackUrl
})
}
const {securePaymentConfirmation, fallbackWindow} = await request.show();
if(securePaymentConfirmation) {
do3DSStuff(securePaymentConfirmation)
return
}
if(fallbackWindow) {
fallbackWindow.addEventListener("message", e => {
if(e.origin == "https://google.com/")) {
//Message from Google Pay window
} else {
//Message from 3DS fallback window
}
})
}
} else {
//Legacy checkout flow
}
It would be ideal if a merchant could construct a PR object and use the output of canMakePayment
to determine if SPC is possible.
const cmp = await pr.canMakePayment()
if(cmp.canDoSecurePaymentConfirmation) {
//Yay! Frictionless, secure, payments FTW!
const result = pr.show()
} else {
//Boo! [fall back to traditional flow]
This would be for the case when the merchant is depending on the browser to provide an instrument selector and then a PH would be invoking SPC
@Goosth and I believe others have pointed out that it would be great to reduce tracking risk by not reusing the same SPC Credential IDs across transactions. Here is an idea:
At enrollment, the relying party provides a public key in addition to other data (e.g., display
information, relying party ID). The relying party associates this public key with the SPC Credential
Identifier they get back through enrollment.
At transaction time, the relying party prepares input for SPC (either for someone like a PSP or their
own usage) by encrypting a hash of an SPC Credential ID along with a time stamp (for example).
The relying party ID and encrypted blob are provided as input to SPC.
The browser follows this algorithm (or similar):
This is a rough idea; I look forward to hearing suggestions to make it realistic and effective.
The SPC spec should provide advice/a threat model on how to securely handle the cryptogram.
Questions:
https://lists.w3.org/Archives/Public/public-webauthn-pay/2020Sep/0009.html
Just wondering what the approach is for Client-side Discoverable Credentials is? (Just for ease, here's their definition).
From the examples the querying function appears to replace it, but that has implications for the inherent advantages of discovery, especially in usability i.e. the RP in this context (the Merchant - not the origin) will "always" have to "know" the Credential identifier. From the perspective of user experience it would great to allow a "natural" selection of the credential at the Authenticator, with some visual prompts for the user.
Leveraging the discoverable credentials approach could allow for the actual payment instrument - lets say it's a payment card - to be stored within the Authenticator using a Level 2 mechanism such as credential properties. This would allow for a belt-and-braces approach where the Authenticator delivers both the authentication event and the secured instrument data.
I haven't "deciphered" the entire spec but the following lines caught my eyes:
While the credential ID may be used as a tracker to correlate a user across origins, an origin (e.g. a merchant) needs to already have access to the credential ID in order to invoke the new API. A merchant also already has a more powerful identifier, i.e. the raw credit card number the user provided, that it used to exchange to the credential ID via trusted server side integration with the issuing bank.
Creating a new system that needs card numbers in clear outside of the RP/Bank environment seems somewhat short-sighted and may even be in conflict with GDPR. Since WebAuthn was built for the Web, wouldn't it be reasonable using Web-methods to overcome deficiencies that stems from technology that was created 25 years ago?
This presentation outlines such a system: https://www.youtube.com/watch?v=OWn8sg7Oy3I&t=306s
That is, payment credentials are also fitted with
making authorizations only reveal which bank the user have.
The hashing mentioned in the presentation has recently got a suitable solution that is 100% JavaScript compatible: https://www.rfc-editor.org/rfc/rfc8785.html
This is effectively a souped-up EMV adapted for the Web. EMV is more "protocol-efficient" than 3DS-like concepts.
Browser-wise I believe this is quite simple to achieve. Backend-wise not so. The ability to support IBAN & friends is probably a necessity to get traction. Note that such systems do not provide any "fallbacks" and such.
Raised in discussion with @gooth:
What does the API do? What is the UX?
Ian and I were chatting today and discovered that we had very different mental models about when SPC authentication is invokable; it seems worthwhile to hash these out and come to a conclusion that we can document (as part of the Scope document?).
Some casual definitions:
PaymentRequest.show()
, such as a web-based PaymentHandler
) and as such is visible to the browser.Given these definitions, I believe that SPC authentication should be invokable in both informal and web-api payment contexts. Do others agree?
Note that if SPC is invokable in informal payment contexts, it is invokable anywhere on the web as the browser cannot tell the difference. It should always require a user gesture, imo, and the browser UX that will be shown should always state a payment context.
I am adding this issue based on various conversations that suggest interest in being able to use SPC in conjunction with a frictionless 3DS flow.
What is the purpose of SecurePaymentConfirmationAction
?
It only has a single valid value so it seems redundant.
We should probably consider other flows defined inside 3DS 2.0.
Consider a situation where the current device has not yet been setup/registered for WebAuthn, but another device linked to this user has have been setup. This is very probable in a Guest shopping scenario.
Out of Band authentication allows the browser to reach out a second device (typically a mobile phone the user carries with them) where the transaction is approved.
Note that we're not talking about using OTP's here, but rather the ability to reach out to a second device and activate the Authentication (e.g. a WebAuthn Platform Authenticator) on that device. If this is is something we can design in should achieve much higher adoption, since a user will not have to have their Fido Authenticator setup on all devices that they own.
The fallbackUrl
parameter in the SecurePaymentConfirmationRequest
dictionary was originally intended to provide an easy fallback mechanism for users of SPC, where they could pass a step-up challenge URL or similar.
This was never implemented inside Chromium, and during the Q4 2020 pilot with Stripe we didn't find any need for it either. Instead, the caller can just handle the rejected promise from request.show()
and utilize their own fallback mechanism.
Unfortunately it is currently marked required
so just removing it from the explainer might be confusing to folks experimenting with the implementation in Chrome today (as the API call will throw with a missing parameter). Instead, I plan to comment it as deprecated in the explainer, then land a change in Chrome to remove it from the IDL, and then finally remove it from the explainer once that reaches Stable (or once all involved parties are aware its being removed).
For the coming Task Force to consider.
The core difference between authentication and authorization is that authentication occurs between two end points, while the result of an authorization may be intended for consumption by a party which is not necessarily even known at time the authorization is created.
Another limitation with authentication is that it more or less presumes that data is in clear and that the authenticating party is "trusted". Merchants are usually not considered as trusted (in computer security terms). That Merchants authenticate card holders is actually a strange thing in itself, this is a task for Issuers.
State-of-the art payment systems like Google Pay encrypt authorization data.
Authorization-based schemes are thus inherently more flexible with respect to backend infrastructure design, but do not combine well with authentication-based schemes like 3DS.
If the authorization path is taken, the most logical foundation would be CTAP2 rather than WebAuthn.
Note: "pure" authorization schemes like EMV do not depend on externally provided "challenge" data, they rely on locally generated nonce objects and time stamped data making "replay" protection / detection straightforward. Replay may even be permitted if services are idempotent, for dealing with network glitches and similar temporary outages.
From the FIDO Web Pay specification:
Through the use of a dedicated credential database, associated FIDO keys can be freely used by the FWP implementation, while remaining invisible and protected from access by all parties but their respective Issuer. |
The code snippet in the explainer suggests that the caller can provide multiple icons. Wouldn't one icon be sufficient?
const publicKeyCredentialCreationOptions = {
paymentInstrument: {
name: 'Mastercard****4444',
icons: [{
'src': 'icon.png',
'sizes': '48x48',
'type': 'image/png',
}],
},
...
Proposal: The Browser could crptographically link the transaction details to the FIDO assertion via an additional field in the "collectedClientData"
When we create a new payment credential using your roaming credential, it's implied that the browser stores the credential displayName
and icon
so that when it's presented as an authentication option in the future, the browser can provide the user that useful bit of context that "this will use your Blue Authenticator to approve putting this purchase on your Yellow Card ending in 9999."
Moving the Blue Authenticator to another computer though and doing the same operation with the same account will prvoide a compatible credentialId
that the browser can then use to approve this new purchase ... but there's no knowledge of what that credentialId
would be approving for. E.g., the context would be, "This will use your Blue Authenticator to approve for an unknown payment type."
Browsers could sync that data around, but many people have many reasons to not sync, so this is a "normal" edge case. What should happen when a browser can't display useful context? Ignore that credential ID?
In the original 3DS setup the ACS is used to assure the Merchant/Card network that the card -holder is authentic. As far as I recall, the result of this authentication is not brought back to the Issuer during the actual transaction request since that would involve changes in the core payment protocol as well as require interaction with the ACS.
Because the current SPC flow diagrams are pretty hard to follow, I'm unable to tell if this part has changed or not.
If this has not changed, I wouldn't characterize the Issuer as the RP.
In the proposal, the new PaymentCredentialInstrument is not linked with enrolled PaymentInstruments associated with the Payment Handler. I think it's worth considering linking the two types of instruments together, especially to support the combined flow where the instrument is selected and then end-user authenticated.
The instrument data (i.e. card art, instrument descriptor) associated with a PaymentCredential
may become stale over time. It would be useful for the original relying party to be able to update the instrument data.
@tblachowicz: Would you mind adding a bit more details on how you envision this fit into a user flow? Do you want this capability when user visits their bank (the RP)? Are you also thinking of push update without the user visiting the RP's website?
Currently, in the pilot implementation, only the domain name of the merchant website is displayed in the payment confirmation dialog. I'd like to propose to enhance the visual representation of the merchant by displaying also the title and favicon of the page from which SPC request has been triggered. Of course, it's the subject of the availability of the data.
Based on some questions by @Goosth:
When more than once SPC credential matches, what requirements are there, and what should be left to browser innovation?
For example:
On the other hand, it might be a requirement for the browser to try to match the requirements in the order specified in the input to the API. This would allow the RP to express preferences, and this could be one way for the RP to say "I want you to go for frictionless first, then low friction."
The explainer says that PaymentCredential
is new type of credential used with Credential Management API. The credential is PublicKeyCredential
with special features. Can the same credential be used for SPC flow and authentication with vanilla WebAuthn?
The use-case I have in mind is this:
Can you please confirm SPC allows such a use-case?
Reposting a question from @ianbjacobs:
Should we include a field for the RP to provide routing information, rather than assuming that the instrument id will carry the routing information in all cases? The field might be optional. But, for example, it would easily map to systems like ACH (routing number, account number).
I would like to hear from people whether it is important to allow credential enrollment outside a Payment Request flow. For example, I visit my payment provider (BobPay) and enroll my authenticator for a future SPC payment.
The explainer says that 'AuthenticatorAssertionResponse.signature' will contain the signature that should be verified by the RP. WebAuthn specification has an exact signing/verifying algorithm for the signature already. In a nutshell, it is the signature over the concatenated authenticatorData
and clientDataJSON
. Is SPC going to overload the signing/verification algorithm to include SecurePaymentConfirmationResponse.paymentData
? If so, it'd be great to understand the details.
I spoke with Frank Hoffmann today who asked whether it would be possible for a payment app that calls SPC (assuming it can) to change the total.
The use case is:
Ian
During checkout, the browser needs to know the WebAuthn credential IDs associated with the instrument to generate an assertion. The issuer can return the credential IDs directly or return the instrument IDs, which the browser will dereference into credential IDs using an internal mapping. The internal mapping is updated when enrolling a new credential.
For the pilot, both options will work equally well.
Post pilot, having the instrument ID abstraction may be useful. For example, it can be used as a new routable ID on the network for the combined instrument selection & authentication flow.
The instrument ID format will be defined by each payment method. So maybe it can also be a composite data structure that contains a credential ID and any other metadata the issuer wants to include.
@adrianhopebailie raised this in https://github.com/rsolomakhin/secure-payment-confirmation/issues/10#issuecomment-666970228:
I worry about the browser-side mapping of instruments to credentials and the high likelihood that the data gets stale.
The simplest approach is for the issuer to update an existing credential with a new label and icon when the user visits the issuer's website. But I wonder if a user cannot be guaranteed to revisit. Perhaps some kind of push mechanism for the RP to update the instrument label and icon would be helpful.
I'm sorry for this issue as it seems slightly off-topic.
As I understand Secure Payment Confirmation, its use cases lay in payments with credit cards or similar?
Are you aware of similar proposed features, that leverage WebAuthn to confirm transactions inside online banking applications?
E.g. PSD2 (via RTS) of the European Union has following requirements for transaction confirmation (with dynamic linking):
RTS - Art 5 - Dynamic Linking
Where payment service providers apply strong customer authentication in accordance with Article 97(2) of Directive (EU) 2015/2366, in addition to the requirements of Article 4, they shall adopt security measures that meet each of the following requirements:
(a) the payer is made aware of the amount of the payment transaction and of the payee;
(b) the authentication code generated shall be specific to the amount of the payment transaction and the payee agreed to by the payer when initiating the transaction.
(c) the authentication code accepted by the payment service provider corresponds to the original specific amount of the payment transaction and to the payee agreed to by the payer. Any change to the amount or the payee shall result in the invalidation of the authentication code generated.
For the purpose of paragraph 1, payment service providers shall adopt security measures which ensure the confidentiality, authenticity and integrity of each of the following:
(a) the amount of the transaction and the payee through all phases of authentication.
(b) the information displayed to the payer through all phases of authentication including generation, transmission and use of the authentication code.
For the purpose of the requirement under point (b) in paragraph 1 and where payment service providers apply strong customer authentication in accordance with Article 97(2) of Directive (EU) 2015/2366 in relation to a card-based payment transaction for which the payer has given consent to the exact amount of the funds to be blocked pursuant to Article 75(1) of that Directive, the authentication code shall be specific to the amount that the payer has given consent to be blocked and agreed to by the payer when initiating the transaction.
For the purpose of the requirement under point (b) in paragraph 1 and where payment service providers apply strong customer authentication in accordance with Article 97(2) of Directive (EU) 2015/2366 in relation to payment transactions for which the payer has given consent to execute a batch of remote electronic payment transactions to one or several payees, the authentication code shall be specific to the total amount of the batch of payment transactions and to the specified payees.
As I understand it, at least payee (e.g. account number) and amount would have to be displayed to the user inside an online banking application.
Based on discussions with various people, I understand that there may be different ways to create the nonce that is used as input to Web Authentication, including:
In short, I have heard that the system should not rely on any single way of creating the nonce.
(That does not mean that every approach will be equally trusted.)
In Secure Payment Confirmation, if authentication fails, who should be responsible for showing the fallback URL inside a Secure Modal Window (a.k.a. payment handler window)?
Option 1 is browser. This is the simplest for merchant/PSP and issuer as serving static web pages are much simpler than hosting service workers. However, there are challenging open questions that need to be thought through:
Option 2 is an issuer-hosted payment handler. This payment handler can be installed when the issuer enrolls a PaymentCredential
and is bound to the newly created credential. Then during checkout time, the browser passes the authentication result (pass or fail) to the issuer payment handler via an extension to the PaymentHandlerEvent
, and the service worker can choose to fall back in an open window or not.
The main benefit of option 2 is that it fits well within the existing web payments architecture, where the security and privacy models are well understood.
When looking at https://github.com/rsolomakhin/secure-payment-confirmation/blob/master/sequence-diagrams/SecurePaymentConfirmation-Pilot-Checkout.png I fail linking it to 3DS. In my understanding, 3DS is a separate (additional) card-holder authentication process performed before the actual payment process is initiated.
In the last part of the mentioned sequence diagram it seems that the ACS is the same as a payment initiation end-point. That is not how the existing 3DS ecosystem works.
Am I missing something here?
I would consider making a less crammed version of sequence diagram where each step is numbered, making it possible to put explanatory data in text outside of the diagram.
Note: this is a question for the coming spec, not the Stripe pilot.
I have not found any information on how SPC is supposed to deal with Account-to-Account payments like SEPA Instant.
A common characteristic of these systems is that they usually do not come with a physical card (and associated printed account number), making it somewhat unclear how they fit a 3DS world. Existing A2A payment systems do not require users to memorize or explicitly type account numbers.
What follows is an alternative flow and API design for SPC that enables a zero-friction flow where the payment network supports this.
Thanks to Gerhard for the idea of using the PReq in this way.
Advantages:
e.g 3DS 2 with legacy card entry form
e.openWindow
to render the fallback step-up authentication UI (e.g. SMS-based OTP or similar)e.g. 3DS 2 with browser-rendered instrument selection or Google Pay
e.openWindow
to render the fallback step-up authentication UI (e.g. SMS-based OTP or similar)Assumptions:
https://bankofamerica.com/web-payments/visa-platinum
NOTE: In a standard 3DS 2 flow the merchant is expected to render an iframe using this URL and the ACS uses this to fingerprint the user's browser. The data collected by the ACS is associated with a
threeDSServerTransID
generated by the merchant.
const request = new PaymentRequest(
[{
supportedMethods: 'https://bankofamerica.com/web-payments/visa-platinum',
data: {
cardholderAccountNumber: '512312346523765',
threeDSServerTransID: '12345678-1234-5678-abcd-eFABCDEF0123'
}
}],
{
total: {
label: 'total',
amount: {currency: 'USD', value: '20.00'}
}
});
PaymentRequestEvent
otherwise the JIT install steps are followed first:HEAD /web-payments/visa-platinum HTTP/2
Host: bankofamerica.com
And the server would then respond:
HTTP/2 204
Link: </pay/payment-manifest.json>; rel="payment-method-manifest"
{
"default_applications": ["https://bankofamerica.com/web-payments/webappmanifest.json"],
"supported_origins": ["https://bankofamerica.com"]
}
The browser will install the Payment Handler that is defined by the app manifest at https://bankofamerica.com/web-payments/webappmanifest.json
If the JIT install fails the PR API will return an error and the merchant falls back to legacy 3DS
NOTE: The PH is not able to do fingerprinting without calling
e.openWindow
and showing the user some UI.
NOTE: Other means of doing client-side user identification are possible given the context. e.g. The PH could use local storage or the WebCrypto APIs to securely identify the user without the need for WebAuthn at this stage.
const response = await fetch("https://bankofamerica.com/3ds/2/risk-check", {
method: 'POST',
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json'
},
redirect: 'follow',
body: JSON.stringify({
threeDSServerTransID: '12345678-1234-5678-abcd-eFABCDEF0123'
cardholderAccountNumber: '512312346523765',
amount: {currency: 'USD', value: '20.00'}
})
})
const {result, spc} = await response.json()
if(result.instrument) {
await e.instruments.set(
result.instrument.key, // "98745678-1234-5678-abcd-er76rqwffd86a"
result.instrument.value);
/* result.instrument.value =
{
name: "Visa ****3765",
icons: [{
src: "/pay/visa.png",
sizes: "32x32",
type: "image/png",
}],
methods: [
"https://visa.com/web-payments/click-to-pay",
"https://bankofamerica.com/web-payments/visa-platinum"
]
}
);
*/
}
if(acsResponse.result === "SKIP-AUTHN"){
e.respondWith(acsResponse)
return
}
if(acsResponse.result === "DO-SPC"){
const {challenge, allowCredentials} = spc
// Internally the browser turns this into a WebAuthn get assertion call
const assertion = await e.securePaymentConfirmation({
instrumentId: result.instrument.key,
challenge,
allowCredentials,
60000
})
if(assertion){
e.respondWith(assertion)
return
}
}
e.openWindow('https://bankofamerica.com/sms-otp/)
....
I want to capture here a request I have heard in discussions about the Stripe pilot:
If Step two implies that Payment Request terminates (and the merchant receives Response data), how would SPC enrollment happen?
My understating of the explainer is that the FIDO Credential ID is used to query the credentials and trigger the SPC flow for the matching credential.
It has been discussed during the annual (virtual) F2F meeting that the new Payment Instrument ID could be introduced instead of the generic FIDO Credential ID. When a new payment instrument gets enrolled in the user agent the Payment Instrument ID would be generated. The ID would be associated with the card meta-data, FIDO Cred ID, and RP ID stored by the user agent. During checkout, the client would provide only Payment Instrument ID(s) in the SPC request and the user agent would find the matching stored credentials based on the FIDO Cred Id and RP ID associated with the Payment Instrument ID.
Reposting a question from @ianbjacobs:
The instrument id is minted by the relying party. Could we design this so that the instrument id never leaves the payment handler or browser?
For example, if the browser returned a hash of the instrument id and the merchant origin, then the merchant could send that hash (and their origin) to the RP and the RP could figure out the underlying instrument id. I realize that may be cumbersome for the RP (e.g., if they have to process a large number of instrument ids). I mention it here as an example of trying to figure out a way to not reveal the instrument id to the PR API caller.
The instrument id visibility problem seems relevant with or without payment handlers.
Raised by @Goosth
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.