digital-credentials icon indicating copy to clipboard operation
digital-credentials copied to clipboard

Add a way to check what protocols are supported

Open marcoscaceres opened this issue 1 year ago • 19 comments

It's currently not possible to check what protocols the browser supports so developers won't know if calling .get() will immediately fail (as it requires user activation).

As such, we should then add a. static setlike.supportedProtocols With the sequence, a developer can easily filter with standard Set methods.

Proposed API addition

interface DigitalCredentialsSupportedProtocols {
  readonly setlike<DOMString>; // user agent pre-populates
};

partial interface DigitalCredential {
   readonly attribute DigitalCredentialsSupportedProtocols supportedProtocols;
}

Usage:

if (DigitalCredential.supportedProtocols.has("openid4vp")) {
  // let's make an openid4vp request
}

// Or you can do...
const supported = Array.from(DigitalCredential.supportedProtocols).filter(typesTheRPSupports);

// Or whatever developers want:
for (const supported in DigitalCredential.supportedProtocols.values()) {
   // do things with supported
}

const requests = [...DigitalCredential.supportedProtocols].map(toRequestWeKnowHowToMake);

// or even... 
switch (true) {
   case DigitalCredential.supportedProtocols.has("openid4vp"):
      makeOpenIDRequest(data);
      break;
   case DigitalCredential.supportedProtocols.has("whatever"):
      makeWhateverRequest(data);
      break;
   default:
     throw TypeError("Oh noes! they don't support our favorite protocol!")
}

That gives a ton of flexibility. You can even use it will all the new fancy JavaScript set comparison operations:

// DigitalCredential.supportedProtocols is a Set
const supportedProtocols = DigitalCredential.supportedProtocols;

// Example Set of protocols to compare
const protocolsToCheck = new Set(['openid4vp', 'someOtherProtocol']);

// Union: Combining both sets
const union = supportedProtocols.union(protocolsToCheck);
console.log(union); // Set { 'openid4vp', 'someOtherProtocol', ... }

// Intersection: Getting common protocols between the sets
const intersection = supportedProtocols.intersection(protocolsToCheck);
console.log(intersection); // Set { 'openid4vp' }

// Difference: Getting protocols supported by DigitalCredential but not in protocolsToCheck
const difference = supportedProtocols.difference(protocolsToCheck);
console.log(difference); // Set { ... } (protocols supported by DigitalCredential but not in protocolsToCheck)

// Symmetric Difference: Getting protocols that are in either set, but not in both
const symmetricDifference = supportedProtocols.symmetricDifference(protocolsToCheck);
console.log(symmetricDifference); // Set { 'someOtherProtocol', ... }

marcoscaceres avatar Sep 12 '24 23:09 marcoscaceres

+1, this is a useful feature to have to ensure that user journeys don't end abruptly when the desired protocol isn't supported.

msporny avatar Sep 13 '24 00:09 msporny

Ideally I'd like browsers on Android to pass the request through and let the OS decide what protocols are supported. So its unclear how a browser could return this list on Android.

I think a isProtocolSupported() method might be better. On Android this would likely always return true.

If we keep this I think it will be very hard for the community to develop or improve the protocols. Lets say OpenID wanted to start work on 'openid4vp_v2'. Today with Chrome/Android they can just start developing and testing it. All they need to do it create a wallet and test website that supports and they can get real implementation experience and iterate before committing it to a stable spec.

We're actually doing that this week by implementing the new query language for openid4vp_v1.1. They need multiple implementations to gain some real world feedback before they can commit to a spec, so we are adding support for it to our wallets and test websites to exercise it. This doesn't require any changes to Android or Chrome.

If we add this API, then browsers couldn't list openid4vp_v1.1 or openid4vp_v2.0 etc...does this mean they shouldn't pass them through? In that case we couldn't iterate on spec proposals without browsers and OSs being in the same iteration loop.

So my recommendation would be to not return a list, but change to a isProtocolSupported('opendi4vp') method. Then on android the browser can defer that question to the platform.

leecam avatar Oct 03 '24 19:10 leecam

Boolean testing is often preferred to providing a set for privacy reasons.

Also a privacy risk though if the test/set reveals any configuration-specific information (whether you have installed a particular wallet that supports a particular feature, etc.), so we should warn against that if we do support this.

npdoty avatar Oct 07 '24 16:10 npdoty

It's currently not possible to check what protocols the browser supports so developers won't know if calling .get() will immediately fail (as it requires user activation).

What about an API similar to WebAuthn L3's getClientCapabilities()?

https://w3c.github.io/webauthn/#sctn-getClientCapabilities

Spitballing a bit here, it might look something like this:

const { openid4vp, whatever } = await DigitalCredential.getClientCapabilities();

if (openid4vp) {
  const cred = await navigator.credentials.get({ digital: { ... } });
} else if (whatever) {
  const cred = await someCustomPresentationAPI({ ... });
}

Also a privacy risk though if the test/set reveals any configuration-specific information (whether you have installed a particular wallet that supports a particular feature, etc.), so we should warn against that if we do support this.

There's accommodations in PublicKeyCredential.getClientCapabilities() for browsers to omit certain capabilities for sake of user privacy; I don't see why those same accommodations couldn't be carried over into a Digital Credentials-specific version of the method.

And others can foresee paired use of WebAuthn + Digital Credentials for certain use cases in the future, it might be nice for developers to have similar methods to call in either domain 🤔

So my recommendation would be to not return a list, but change to a isProtocolSupported('opendi4vp') method. Then on android the browser can defer that question to the platform.

To @leecam's question, a getClientCapabilities() method would give browsers a chance to query the platform "just in time" to understand available capabilities too.

MasterKale avatar Oct 07 '24 19:10 MasterKale

@marcoscaceres and I chatted briefly about this today and so far we think this could work between Chrome and Safari if we introduced the notion of a pre-defined value of * or any to mean "the browser accepts any protocol" (such that Chrome can expose * and Safari reserves the right to skip it).

samuelgoto avatar Oct 14 '24 22:10 samuelgoto

That seems... messy to be honest. What were the concerns with the method taking an input parameter with a protocol name?

timcappalli avatar Oct 15 '24 00:10 timcappalli

That seems... messy to be honest. What were the concerns with the method taking an input parameter with a protocol name?

Ah, I remembered why @marcoscaceres and I arrived at *: Chrome allows any protocol to be used, whereas Safari allows some, and it seemed important that Verifiers know the difference between the two in an interoperable way.

Specifically, Chrome deliberately wants to allow protocols to be extensible, so that innovation can happen in this space without it being a blocker.

So, if we go with a a static setlike supportedProtocols as @marcoscaceres proposed, there needs to be an entry there that represents this specific difference between the two implementations.

If we go with getters, we wouldn't need to specify *, which would work too, I think.

I don't personally feel strongly which way we go, just wanted to raise that there is an important distinction between Chrome's implementation and Safari's implementation that's important to expose to developers in an interoperable way.

samuelgoto avatar Oct 28 '24 17:10 samuelgoto

Wouldn't this just become a fingerprint vector of which wallets the user has enabled since the browser would just be exposing the protocols supported by the wallet?

For example, if protocol X is only supported by Germany and protocol Y is supported by 3 EU nations, and protocol Z is supported by Bhutan then this becomes a location based inference based on the wallet the user in use. Do we have sufficient evidence that values supplied here aren't diverging based on wallets and country specific regulations and instead the divergence only occurs at the credential format layer? If not, I'd argue this is going to turn into a location based fingerprint and we should limit that risk by not exposing this information.

Also, just to confirm the value here won't be like OIDC4VP+mDL or OIDC4VP+W3C+BBS-SD will it?

A strawman alternative I would suggest would be to decouple the request format passed on via the API from the protocol layer such that the browser translates the API request into the protocol specific format. This way we don't need to reveal the protocol supported.

Also, what's the expected result if there's no matching protocol? Will the user be limited from accessing the site because of this and be forced to switch browsers?

kdenhartog avatar Nov 19 '24 00:11 kdenhartog

2024-11-18 call: this wouldn't actually provide a lot of value if some clients will just always return true.

A separate issue will be opened for client capabilities (such as cross-device availability, "a wallet being available", etc).

timcappalli avatar Nov 19 '24 14:11 timcappalli

@marcoscaceres I don't quite remember the use case that led us to want to have the ability to check for protocol support, but I think it was along the lines of the following:

How would a website be able to test, in advance, if a certain protocol (emphasis on protocol, not installed wallet) is supported by the platform?

For example, if a website wanted to degrade gracefully (e.g. throw a custom scheme, hide the UX affordance) in the absence of protocol support in the API, I think they'd have three options:

  1. Make the DC API call and degrade gracefully when it gets an error back
  2. Use a Web Platform API that allows the website to test the support for the protocol ahead of time
  3. Test the user-agent string and special case browser implementations

(3) seems the least great option to me. It is unclear to me if (2) is preferred to (1).

samuelgoto avatar Dec 02 '24 17:12 samuelgoto

One possibility for (1) is to define an error code that we always use for such cases of the platform rejecting without showing the user any UI. Separating platform non-support from other types of failure sounds like a good thing to be doing regardless. Given that we know most use cases are going to be falling back to custom schemes in practice anyway, perhaps that's sufficient?

I worry that today many of the hypothetical arguments really boil down to the UI concern that stems from not being able to predict in advance whether a request can be satisfied (where no matching credential is going to be the common case for a long time).

Personally I'd prefer we wait to hear from some developer about a real-world scenario they have where an API like this would actually meaningfully help them (even without a solution to the 'has matching credential' UI problem).

RByers avatar Dec 02 '24 18:12 RByers

if a certain protocol (emphasis on protocol, not installed wallet) is supported by the platform?

@samuelgoto could you expand on your thinking here? Since the wallet is effectively processing the request, wouldn't the platform just be effectively revealing the results of what the wallet supports?

For example,

wallet A supports well-known-protocol and cool-new-protocol wallet B supports well-known-protocol wallet C supports cool-new-protocol wallet D supports well-known-protocol

Wouldn't querying this produce a fingerprint like this?

[well-known-protocol] = wallet B or D [cool-new-protocol] = wallet C [well-known-protocol, cool-new-protocol] = wallet A

I don't think we want a scenario like this where a site can infer about the existence of an app on the device. Are you saying there's a case where the actual browser platform doesn't support cool-new-protocol irregardless if wallet A or wallet C are in use and in the case of wallet C, it just won't work with that browser?

In any case, if we go the path of option 1 I think we head in the direction of avoiding the fingerprinting concern to a reasonable degree.

kdenhartog avatar Dec 03 '24 00:12 kdenhartog

One possibility for (1) is to define an error code that we always use for such cases of the platform rejecting without showing the user any UI. Separating platform non-support from other types of failure sounds like a good thing to be doing regardless. Given that we know most use cases are going to be falling back to custom schemes in practice anyway, perhaps that's sufficient?

I think that would a reasonable variation of (1) or (2).

samuelgoto avatar Dec 03 '24 01:12 samuelgoto

Since the wallet is effectively processing the request, wouldn't the platform just be effectively revealing the results of what the wallet supports?

Nope. We are discussing here a test that tests whether the browser supports a specific protocol (Chrome supports any, Safari is planning to support some and block others -- ack @marcoscaceres ?), not whether an installed wallet does.

samuelgoto avatar Dec 03 '24 01:12 samuelgoto

Wouldn't it for the European Digital Identity use case (and maybe others) be as important what the wallet supports? Otherwise it will be difficult for developers and websites to predict and inform the user if conditions aren't met, all they can do then is fail gracefully.

sunetzacharias avatar Dec 03 '24 10:12 sunetzacharias

Editor's discussed what "it" means:

  • the browser knows about it (does necessarily support it)
  • does not mean any wallet support it or any wallet.

So, DigitalCredential.knownProtocols might be more appropriate as a name.

marcoscaceres avatar Jan 15 '25 23:01 marcoscaceres

@marcoscaceres — You said

  • the browser knows about it (does necessarily support it)

Maybe (emphasis mine), "(does not necessarily support it)"?

TallTed avatar Jan 21 '25 19:01 TallTed

Editor's discussed what "it" means:

the browser knows about it (does necessarily support it) does not mean any wallet support it or any wallet. So, DigitalCredential.knownProtocols might be more appropriate as a name.

This makes sense and doesn't present a large concern to me. It's effectively just a browser fingerprint then rather than App install fingerprint, but someone can look at the UA if that's what they want. In which case, this isn't somehow degrading privacy.

kdenhartog avatar Jan 22 '25 00:01 kdenhartog

@marcoscaceres With a potential .knownProtocols solution to this issue, what should a browser that wants to support all possible protocols, and takes the stance that it therefore knows all protocols, return in .knownProtocols? That'd potentially require the browser to keep track of all possible protocols, even potentially ones not in the registry, and that seems infeasible...I get that's why '*' was entertained as a magic value to wave away this question, but I agree the ergonomics of this are odd. Are there examples of other similar existing browser APIs that use this type of value to solve a similar problem?

MasterKale avatar Apr 24 '25 02:04 MasterKale

@marcoscaceres can we close this since #221 landed?

timcappalli avatar Oct 31 '25 21:10 timcappalli

Closed via https://github.com/w3c-fedid/digital-credentials/pull/221

marcoscaceres avatar Nov 05 '25 04:11 marcoscaceres