w3c / secure-payment-confirmation

Secure Payment Confirmation (SPC)
https://w3c.github.io/secure-payment-confirmation/
Other
106 stars 48 forks source link

Proposal: add Relying Party id as a required input to SPC authentication #164

Closed stephenmcgruer closed 2 years ago

stephenmcgruer commented 2 years ago

Background

Currently, SPC takes as input a list of credential IDs, but does not take the Relying Party ID as input. Instead, we currently 'magically' obtain it during the "Steps to respond to a payment request".

Currently in Chrome, that magic is part of the browser-local storage that we intend to get rid of (long-term). We had previously envisioned that in the future, the magic would be delivered by authenticator-level support, e.g. as part of our assumed use of a Conditional UI-like API.

However, in talking with the WebAuthn folks, it has become clear that having the RP ID as an input would be beneficial. Authenticators often internally store discoverable credentials as an RP id --> [credential] structure, and so looking up from a credential ID to an RP ID could be quite expensive. Additionally, Conditional UI presumes that the RP ID is known, and so it will be easier to 'piggyback' on that API support if we also have the RP ID as an input.

Having the RP ID as an input would also solve another edge-case problem - what happens if the SPC caller passes in credentials from multiple different RPs? Today this is undefined; the behaviour will be reasonable but inconsistent. By requiring the RP ID as input, it becomes clear - any credential ID from a different RP will simply not match.

Proposal

Add RP ID as a required member of SecurePaymentConfirmationRequest:

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

This RP ID would be a claim from the SPC caller as to whom the RP is for the passed credentials. This claim would be verified by the authenticator as part of the flow (as the credential IDs wouldn't match if the wrong RP ID was provided).

Alternative - optional RP ID

We could make the RP ID an optional field. If not passed, the browser would use the current origin as the RP ID, similar to how WebAuthn get() works today. This is mostly useful for supporting the 1p payments case - all 3p use-cases would need to pass the RP ID.

Implications

Backwards compatibility

This change would not be backwards compatible. Existing users of SPC would have to update their implementations to pass rpId. They could do this ahead of any browser-side change, as passing an unused member in a dictionary is valid.

3DS

SPC was integrated into the 3DS v2.3 specification. Naively, the existing integration would not support adding an RP ID field. I have some ideas on this which we should probably take to the 3DS WG and see what they think :).

Goosth commented 2 years ago

This makes sense. I support that we add this to the specification, and sooner rather than later. It will set us up for the future.

@stephenmcgruer would be curious to understand your ideas around the 3DS specification a bit better.

stephenmcgruer commented 2 years ago

Thanks @Goosth . I've put together a document with some of my thoughts on this, which we can hopefully discuss early next year: https://docs.google.com/document/d/1uVydbd046aAM_lQd2McEDCM6ttJOh0ggqL5J5TxhZEY/edit?usp=sharing

stephenmcgruer commented 2 years ago

Just an FYI for anyone looking at this issue; adapting to this change requires a code change similar to the below in your javascript, and can be done anytime ahead of the change rolling out in browsers (i.e., it is forwards-compatible):

const request = new PaymentRequest([{
  supportedMethods: "secure-payment-confirmation",
  data: {
    credentialIds,
+    rpId: "relyingparty.com",
    challenge: ...,
    instrument: { ... }
    payeeOrigin: ...,
    timeout: ...,
  }], {
    total: { ... } 
  });

The rpId ("relyingparty.com") is whichever RP id was used when registering the SPC credential in the first place.