Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using CredentialsContainer.get() instead of Payment Request API #56

Open
tblachowicz opened this issue Apr 26, 2021 · 12 comments
Open

Using CredentialsContainer.get() instead of Payment Request API #56

tblachowicz opened this issue Apr 26, 2021 · 12 comments
Labels

Comments

@tblachowicz
Copy link
Contributor

tblachowicz commented Apr 26, 2021

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?

@stephenmcgruer
Copy link
Collaborator

I am not aware of any clear benefits of using the Payment Request API over the Credential Management API, but I'm also not aware of clear benefits of using the Credential Management API over the Payment Request API :D.

In either case, we need to bring together payment information and authentication information. As you note, in the initial proposal (and in what we implemented and trialed in Chromium), we brought authentication information into Payment Request. We did this because it was the Web Payments team that was working on this API, not the folks who are involved with the Credential Management API.

Do you see a clear benefit to using the Credential Management API?

(cc @equalsJeffH @mikewest )

@tblachowicz
Copy link
Contributor Author

At the moment I cannot really point out any clear technical benefits of using CM API over PR API. However, I think the SPC is closer conceptually to CM API than it is to PR API, especially if we also consider enrollment of the payment instrument as well.

Credential Management API provides a way for the website to store/create and retrieve the different types of credentials via the convenient API. When we consider SPC it's also about storing and retrieving the credentials of a specific type. CM API provides abstract classes and interfaces that are extended to support the specific types of credentials e.g. password or public key. I can imagine SPC extending the CM API to allow the client to provide SPC-specific parameters to both create and retrieve the SPC-specific credentials.

If we'd decide to use PR API to trigger retrieval of the SPC Credentials we still need to consider how to create/store the credentials in the user-agent. If I'm not mistaken the initial proposal actually suggests using Credential Management API to do so. Therefore, why not use Credential Management API also for storing/creating the credentials?

@adrianhopebailie
Copy link
Collaborator

The purpose of using the Payment Request API is that this provides a structured data object with the transaction details.

It's natural for a developer to request a payment via a payment API, where SPC is part of that flow. Admittedly it might be the only required part of that flow but it is also possible that the flow includes invocation of a Payment Handler or prompting the user to select between multiple payment instruments that match the conditions provided in the request.

@equalsJeffH
Copy link

I agree with @adrianhopebailie: use a payments-focused API for payments.

@tblachowicz
Copy link
Contributor Author

OK. It seems people favor Payment APIs over Credential Management APIs. I think it's difficult to construct objective arguments, so this is based on preferences. Good to have a discussion anyways!

@adrianhopebailie
Copy link
Collaborator

@tblachowicz I wouldn't say it's just personal preference. By using payment specific APIs we solve a lot of privacy challenges for the browser.

When a website invokes a payment API (as opposed to a general purpose API) then the browser can assume the context is a payment and can do things like show payment specific UI that would expose malicious websites that attempt to use the API for non-payment uses cases (tracking or farming data).

@ianbjacobs
Copy link
Collaborator

Labeled after-v1 based on 3 March 2022 WG discussion https://www.w3.org/2022/03/03-wpwg-minutes

@stephenmcgruer
Copy link
Collaborator

Hey folks,

The following is a proposal as to what moving to navigator.credentials.get might look like, and some of the pros/cons. On the Chrome side, we are definitely still unsure about what the right path is forward here - we have found basing this on PaymentRequest to be quite awkward and a source of implementor bugs, but also it is not clear that navigator.credentials.get() would be better-enough to be worth the switch!

Background

Secure Payment Confirmation currently uses PaymentRequest as its API entry point:

// On, e.g., merchant.com
const request = new PaymentRequest([{
  supportedMethods: 'secure-payment-confirmation',
  data: {
    credentialIds: [  ],
    rpId: 'bank.com',
    challenge: ...,
    instrument: {
      displayName: 'Fancy Card ****1234',
      icon: 'https://fancybank.com/card-art.png',
    },

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

    timeout: 360000,
  }],
  {
    total: {
      label: 'Total',
      amount: { currency: 'USD', value: '5.00' }
    }
  });

try {
  const response = await request.show();
  await response.complete('success');

  // response.data is a PublicKeyCredential, with a clientDataJSON
  // that contains the transaction data for verification by the
  // issuing bank.
} catch (e) {
  // Handle SPC cancellation/failure, e.g. by presenting an
  // alternative authentication method.
}

This was done for a few reasons that I am aware of:

  1. The Web Payments Working Group (WPWG) was used to working with PaymentRequest as an API surface, and so naturally biased itself that way.
  2. Originally it was not clear that SPC would be closely tied with WebAuthn - currently this is no longer true and SPC directly uses WebAuthn.

However, using PaymentRequest for SPC also comes with downsides:

  1. Mismatch in purpose - PaymentRequest is meant to handle payment experiences (e.g., processing a full payment via a payment app), whilst SPC is (arguably) more of an authentication experience tailored towards payments.
  2. PaymentRequest has a number of sub-APIs that make little sense for SPC:
    • canMakePayment() - for SPC, this is just a feature-detection method.
    • hasEnrolledInstrument() - has no use for SPC.
    • PaymentResponse.retry() - can only be called on a successful SPC invocation (failure rejects the promise instead), and so has little/no use for SPC.
    • PaymentResponse.complete() - unnecessary for SPC, but developers have to call it anyway or Bad Things Happen.
    • Asking for shipping addresses or contact info - Prohibited with SPC.
    • Specifying multiple payment methods in one request - Prohibited with SPC.
  3. A PaymentHandler hosted payment app (e.g., Google Pay) cannot invoke SPC, as PaymentRequest doesn't allow nested calls.
    • For Chrome, this is more of a technical constraint currently, and would not be automatically fixed by changing API surface.
  4. PaymentRequest.show() requires a user gesture, whilst WebAuthn’s credentials.get() does not. We are currently considering which is correct in Proposal: Remove User Activation requirement for authentication #216

The Proposal

Move SPC to be called by web developers via navigator.credentials.get(). This is largely straight-forward as SPC already defines an internal WebAuthn extension. We would expose that extension and let developers use it directly instead of via PaymentRequest:

// On, e.g., merchant.com
navigator.credentials.get({
  publicKey: {
    allowCredentials: [...],
    rpId: 'bank.com',
    challenge: ...,
    timeout: 360000,
    extensions: {
      payment: {
        payeeName: 'Merchant Shop',
        payeeOrigin: 'https://merchant.com',
        total: {
          currency: 'USD',
          amount: '5.00',
        },
        instrument: {
          displayName: 'Fancy Card ****1234',
          icon: 'https://fancybank.com/card-art.png',
        },
      },
    },
  }
});

There are two changes from the AuthenticationExtensionsPaymentInputs as currently specified in SPC: the rpId field is removed as it is represented elsewhere in the get() call, and the topOrigin field is removed as we have to calculate that in the browser and not allow the website to specify it.

Pros / Cons

(A possibly incomplete list!)

Pros:

  • Move SPC away from being a 'payment' thing and towards it being an 'authentication' thing.
  • Simplify the SPC API for developers:
  • Make it easier for SPC to support future changes to WebAuthn, e.g. the newly added get()-time parameters for attestation.
    • Currently we have to add these new parameters to SPC as pass-throughs to the underlying WebAuthn call.
  • Very hand-wavy and hypothetical - but if we were to support SPC for native Android Apps, we think we would make it use the same API surface as WebAuthn for native Android Apps, rather than invent PaymentRequest at the Android layer.

Cons:

  • Move SPC away from being a 'payment' thing and towards it being an 'authentication' thing.
    • (Yes, deliberately included in both lists!)
  • Some folks have suggested having a non-WebAuthn backed version of SPC for cases where authentication should be 'lighter', e.g. using WebCrypto instead, and this API change would impede that.
  • Awkward fallback behavior in non-supporting web browsers (see below).
  • SPC would be a fairly complex WebAuthn extension, if we spec'd all the behavior in the extension.
    • However note that devicePubKey already exists as an extension and is likely more complicated that what we would define.
  • Churn of an already-shipped API, will require a deprecation effort from Chrome.

Fallback Behavior // Feature Detection

A strange consequence of moving SPC into a 'public' WebAuthn extension is that - unlike other WebAuthn extensions - the fallback behavior in a non-supporting browser can be actively undesirable for the calling website. Instead of rejecting the get() call, a non-supporting browser will ignore the payment extension entirely and show a normal WebAuthn experience to the user, producing a WebAuthn cryptogram. However if the caller requires payment details in the cryptogram, this cryptogram is useless to them and the user's time has been wasted!

Note: this only happens in the '1p' SPC case, where the current origin is also the Relying Party. In the '3p' SPC case, the get() call immediately rejects in a non-supporting browser as the rpId will mismatch. However the '1p' SPC case is still of significant importance!

To address this, we propose introducing a static method on PublicKeyCredential to indicate support (or lack thereof) for SPC:

partial interface PublicKeyCredential {
    static Promise<boolean> isSecurePaymentConfirmationAvailable();
};

This is very similar to the proposal in #81 already for PaymentRequest. It is also similar to the existing methods for detecting support for a user-verifying platform authenticator or for conditional mediation.

Summary

In summary, we think it would be feasible to move to navigator.credentials.get. It would solve some of the awkwardness of the current API surface, but would also present its own challenges.

We would be interested in discussing this at the Working Group, but most interested in hearing from developers who have already or are currently experimenting with SPC to see how much the API shape impacts them!

@ianbjacobs
Copy link
Collaborator

cc @dcrousso

@ianbjacobs
Copy link
Collaborator

Thanks, @stephenmcgruer. I would add as a "pro" that not having to also implement Payment Request API could make SPC much easier for browser makers to implement.

@Goosth
Copy link
Collaborator

Goosth commented Nov 23, 2022

Thanks for a great writeup Stephen. You raise some very valid points.

This is a very important discussion that will have long term implications.

There's an innate difference between a Transaction/Payment and a 'Login'. A classic login gives access to a set of 'scopes' on an account to view some fields and potentially to change things (but typically not without separate consent). A payment is an specific event that fundamentally alters your account (you have less money in your account). Fraud patterns and attacks are different, and how customers need to understand the difference between the two are different as well.

I believe that most of the Fido/WebAuthn community see the real focus for WebAuthn as solving the 'login-problem'/getting rid of passwords. In fact, @timcappalli yesterday posted over on WebAuthn re-iterating that view: w3c/webauthn#1823 (comment)

The second reality that we have to face is that the international payment community, and regulations around payments are focusing a lot on enabling customers with lower friction options. The Europe+UK mandates SCA (Strong Customer Authentication), but there are many exceptions allowed, e.g. Risk based or low value transactions, where SCA is not needed. The rest of the world also needs payments but does not require SCA. For most transactions however we still want a consistent and trusted customer consent (e.g. confirming a low value transaction amount), even if the strong auth component for Fido is not required. We are seeing merchants and card associations press for lower friction flows during transactions, and for this to be adopted, we need to define strategies that will allow these options and reduce the steps required.

The Login problem is front and center in the design of WebAuthn; not payments. And there is not a payment focused community contributing there to provide input/directly shaping the API's. With a specific payment (or event) focused API, we can accommodate changes direclty and ensure that this API delivers to industry requirements.
Aligning closely with WebAuthn API gives us access to great industry momentum and progress, but input for payment specific use-cases may be limited since it will be measured against different use-cases.

Tough call indeed.

At this stage, I'm leaning towards rather seeing if we cannot simplify PaymentRequest/Secure Payment Confirmation invocation; keeping the payments focus, rather than blending it with Login. It can still be intrinsically linked to Fido for the 'step-up' / High trust use-case as authenticator and make that linking easy. That will allow the SPC use-case and interface to be driven by the payment community, based on best practices, industry feedback and regulation whereby SPC can be adjusted to deliver for better outcomes for payments (high success rates and low fraud).

Would be interested to hear other perspectives too.

@adrianhopebailie
Copy link
Collaborator

I'm strongly in favour of the proposal from @stephenmcgruer

In my previous comments I have been strongly in favour of "a payment API for payments" so I am sympathetic to @Goosth argument but I think the current implementation of SPC in PR API is the worst of both worlds.

PR API was always intended to kick off a payment flow facilitated by the browser and SPC was intended to be one way to do auth inside that flow (not replace that flow). I still think invoking SPC from within a Payment Handler context warrants further exploration for example.

To address some of the challenges:

Move SPC away from being a 'payment' thing and towards it being an 'authentication' thing

credentials.get() is not an AuthN API. When invoked with a PublicKeyCredential we call it WebAuthN but that name is unfortunate. What the API does is request that a challenge is signed using a key an RP can be confident is protected by the platform (software and hardware) and that it knows it provisioned to a specific user in the past.

The common use case for this primitive is to authenticate a user by getting the user to sign an RP-generated challenge with a known key and then assuming this proves that the current user is the same one that the key was provisioned for. I.e. The usefulness of this API for the AuthN use case depends entirely on how key provisioning is done and how the RP authNs users during that process.

SPC is a new use case for the same basic primitive, except in this case:

  1. there are transaction details in the signed data, not just a challenge, and
  2. the RP has some confidence that what the user saw in the UI is what was signed by the key

TL;DR: I don't believe that using the Credentials API makes SPC an 'authentication' thing, it is explicitly invoked via a "payment" extension to a PublicKeyCredential. It's authentication of a transaction, which is the essence of payments.

Some folks have suggested having a non-WebAuthn backed version of SPC for cases where authentication should be 'lighter', e.g. using WebCrypto instead, and this API change would impede that.

Personally, I think the same should be used for light-weight transaction signing use cases too where the 'allowedCredentials` could include keys provisioned using WebCrypto and not platform authenticators.

This would allow the browser to select the lowest friction signing ceremony based on the keys the RP has said it will allow for the current transaction.

Or, an entirely new credential type could be defined which takes the same input as the PublicKeyCredential payment extension but is also provisioned and exercised via the Credentials API.

SPC would be a fairly complex WebAuthn extension, if we spec'd all the behavior in the extension.

Seems like a poor reason to not deliver a better API. Priority of constituents etc.

Churn of an already-shipped API, will require a deprecation effort from Chrome.

We have always been told that SPC as a payment method in PR API as a temporary thing. I'd be pretty disappointed if "churn" was the excuse for not fixing the current SPC API.

Awkward fallback behavior in non-supporting web browsers (see below).

Feature detection seems like a perfectly good solution to this issue. Could we also make feature detection possible by simply defining a new interface/dictionary for the input to the API?

E.g. Something like:

    [SecureContext, Exposed=(Window)]
    interface PublicKeyCredentialPaymentExtension {
      [Default] object toJSON();
      readonly attribute DOMString payeeName;
      readonly attribute DOMString payeeOrigin;
      readonly attribute PaymentCurrencyAmount total;
      readonly attribute PaymentInstrumentDisplay instrument;
    };

I think that would allow a simple:

const spcSupported = ("PublicKeyCredentialPaymentExtension" in window);

Finally, I mentioned previously in this issue that we should have use case specific APIs to assist the browser with context. I think this API as proposed by @stephenmcgruer still adheres to that. It's easy to tell if this is a login or a payment context and browsers can apply this heuristic in how they handle each case when evaluating privacy and security concerns.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants