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

Add new getClientCapabilities method #1923

Merged
merged 30 commits into from
Jan 10, 2024
Merged

Add new getClientCapabilities method #1923

merged 30 commits into from
Jan 10, 2024

Conversation

timcappalli
Copy link
Member

@timcappalli timcappalli commented Jul 18, 2023

This PR adds a new method, getClientCapabilities() and a matching enum for the response (currently hybrid-transport and client-pin-entry).

These capabilities can help an RP craft a sign in or registration experience based on client capabilities. For example, an RP that requires user verification may not want to offer security key registration on a client where PIN entry is not supported. Another example would be a smart TV or kiosk where only hybrid is available (no local platform authenticator). In this scenario, offering a username field doesn't make a lot of sense and a "Sign in with a passkey" button would offer a better user experience.


💥 Error: connect ETIMEDOUT 173.230.149.95:443 💥

PR Preview failed to build. (Last tried on Jan 9, 2024, 6:30 PM UTC).

More

PR Preview relies on a number of web services to run. There seems to be an issue with the following one:

🚨 CSS Spec Preprocessor - CSS Spec Preprocessor is the web service used to build Bikeshed specs.

🔗 Related URL

If you don't have enough information above to solve the error by yourself (or to understand to which web service the error is related to, if any), please file an issue.

@timcappalli timcappalli marked this pull request as ready for review July 18, 2023 21:07
@Kieun
Copy link
Member

Kieun commented Jul 18, 2023

I'm happy to have such features to enhance the UX and for RPs to have some controls. But, adding each function for specific features would not be scale.
As far as I remember, there were some feature requests exposing supported features by clients.

At that time, browser vendors were not comfortable to expose such features to the relying parties due to privacy issues.
But, I'm thinking it's time to reconsider it.

@emlun
Copy link
Member

emlun commented Jul 25, 2023

Would this make these expressions (ignoring Promise indirection) equivalent?

  • isPasskeyPlatformAuthenticatorAvailable()
    
  • isUserVerifyingPlatformAuthenticatorAvailable()
      || getClientCapabilities().includes("hybrid-transport")
    

@timcappalli
Copy link
Member Author

@emlun logically, yes. isPPAA() is for the masses (basic use cases), whereas gCC() is for more advanced use cases, and can easily be expanded.

index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
index.bs Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
index.bs Outdated Show resolved Hide resolved
@MasterKale
Copy link
Contributor

I think it would be great if we added an equivalent capability enums for isUVPAA() to this as well, and maybe isPPAA()? Maybe the conditional mediation check too? The idea is future capabilities checks get added as enums to this, and maybe there are convenience methods added too that make an appropriate call to this method behind the scenes.

@emlun
Copy link
Member

emlun commented Jul 27, 2023

Some discussion points from the 2023-07-26 WG call:

  • (Fixed) client-pin-entry capability is too blunt; some external authenticators support internal UV, but the absence of this capability would make it seem like UV is not supported for those authenticators.
  • Given that this together with isUVPAA() duplicates isPPAA(), I don't think this and isPPAA() should coexist. I prefer this getClientCapabilities() approach over isPPAA(), since the former is extensible and more explicit. If we merge this, I think we should revert Add new isPasskeyPlatformAuthenticatorAvailable() method #1901. (sorry Tim, no offense!)

@emlun
Copy link
Member

emlun commented Jul 27, 2023

it would be great if we added an equivalent capability enums for [...] Maybe the conditional mediation check too?

Note that isConditionalMediationAvailable() is defined in the Credential Management spec, and WebAuthn only overrides it, so unlike isPPAA() we cannot delete isConditionalMediationAvailable() in favour of getClientCapabilities(). So again in the interest of de-duplication, I think we should keep the conditional mediation check separate.

user-verifying platform authenticator is not a client capability

How is it not?

But perhaps that's a moot point anyway. Again, I don't think there should be both a uvpaa capability and the isUVPAA() method, and removing isUVPAA() at this point would be a breaking change. Making it deprecated isn't very appealing either since we probably can't ever actually follow through with deleting it.

@timcappalli
Copy link
Member Author

Given that #1923 (comment), I don't think this and isPPAA() should coexist. I prefer this getClientCapabilities() approach over isPPAA(), since the former is extensible and more explicit. If we merge this, I think we should revert #1901. (sorry Tim, no offense!)

@emlun I think I'd rather withdraw this PR than lose isPPAA() as that new method is more valuable to a larger number of developers.

@emlun
Copy link
Member

emlun commented Jul 27, 2023

Ok, let's hear some more opinions before we decide either way (keep isPPAA(), keep getCC() or keep both).

In the meantime, this just occurred to me: perhaps instead of a list of values, this should be structured as object attributes like the credProps output?

Say that we add a new capability value for a feature that already exists in some clients. Some clients immediately update and start reporting the new enum value, but some clients are slower to update and for a while don't report the new value - even though they do in fact support the feature. This leads RPs to incorrectly conclude that the feature isn't available.

So perhaps it would be better to structure the data model like this?

partial interface PublicKeyCredential {
    static Promise<ClientCapabilities> getClientCapabilities();
};
dictionary ClientCapabilities {
    boolean hybridTransport;
    // Additional properties can be added here
};

so then, if cap = await getClientCapabilities():

  • cap["feature"] === true: feature is known to be supported
  • cap["feature"] === false: feature is known to be not supported
  • cap["feature"] === undefined: feature availability is unknown

@MasterKale
Copy link
Contributor

MasterKale commented Jul 27, 2023

Note that isConditionalMediationAvailable() is defined in the Credential Management spec, and WebAuthn only overrides it, so unlike isPPAA() we cannot delete isConditionalMediationAvailable() in favour of getClientCapabilities(). So again in the interest of de-duplication, I think we should keep the conditional mediation check separate.

In response to this point, I don't want a point I made earlier to get glossed over:

...The idea is future capabilities checks get added as enums to this, and maybe there are convenience methods added too that make an appropriate call to this method behind the scenes.

I don't see getCC() replacing methods like isPPAA(). Rather I see there being value in a method isPPAA() essentially calling getCC(['uvpaa', 'hybrid']) behind the scenes. There's a developer usability win in having "convenience methods" like isPPAA() to make it easier to use (and provide guidance on using a la https://passkeys.dev) WebAuthn, while still enabling RP's with more advanced use cases to drop down a level and call getCC() directly.

@Firstyear
Copy link
Contributor

...The idea is future capabilities checks get added as enums to this, and maybe there are convenience methods added too that make an appropriate call to this method behind the scenes.

I don't see getCC() replacing methods like isPPAA(). Rather I see there being value in a method isPPAA() essentially calling getCC(['uvpaa', 'hybrid']) behind the scenes. There's a developer usability win in having "convenience methods" like isPPAA() to make it easier to use (and provide guidance on using a la https://passkeys.dev) WebAuthn, while still enabling RP's with more advanced use cases to drop down a level and call getCC() directly.

I am worried we are getting into "too complex" territory. It's always the case that with standards like webauth, oauth2, oidc, ldap, or anything else, that while the standard may support 100 different options, what evolves is a small subset that is actually used day to day by implementors. For example, LDAP spans many rfc's with complex search options and extensions, and in reality most people just use search and bind.

I'm worried we are heading down this path - most RP's will never use 90% of what is being created here like getCC or isPPAA because these features end up causing UI/UX issues rather than solving them.

For example, isUVPAA() exists and is now being forgotten by the wayside because we keep chasing these micro UI/UX tweaks that no one seems to need. isPPAA is probably going to end up the same, as RP's realise that security keys like yubikeys exist and they can't just force users to use their phones with extremely slow and awkward cable workflows (did you know in latency it takes ~15 seconds to use phone for cable in AU? And this doesn't even include all the time to get the camera open and dive through all the choose your own adventure menus chrome gives you) .

Even getCC querying for hybrid begs a lot of questions to what you are trying to achieve since the browser itself is already going to offer cable as an option without you needing to tweak anything. I don't think many rp's really even need this capability, since the user should be the one choosing what they want.

I think you need to expand on what you are trying to actually achieve here and how that works for the positive and inverse cases, because currently this seems like an internal MS detail or plan leaking into the specification rather than a change that I can broadly see RP's embracing.

@agl
Copy link
Contributor

agl commented Jul 31, 2023

Ok, let's hear some more opinions before we decide either way (keep isPPAA(), keep getCC() or keep both).

Chrome doesn't believe that existing functions should be removed, but a) we are against adding more and more static methods to PublicKeyCredential in the future, and b) favour having this function return values that duplicate the return values of the existing feature-detection functions so that sites could just call this for all their needs.

Copy link
Contributor

@MasterKale MasterKale left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving to clear up my own request for change since I made the changes I requested.

@MasterKale
Copy link
Contributor

Tagging @emlun @agl @pascoej @jschanck to confirm the change to record<DOMString, boolean> with camelCased values. Given that next week seems to be our last chance to get this merged to meet some browser vendor deadlines I'm trying to get your eyes on this to finalize any additional changes async, so that next week's meeting can involve an uneventful merge.

index.bs Show resolved Hide resolved
Copy link
Member

@emlun emlun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing maplike to record looks good to me, thanks for catching that!

index.bs Show resolved Hide resolved
Copy link
Contributor

@ve7jtb ve7jtb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@MasterKale MasterKale merged commit 80b478c into main Jan 10, 2024
2 checks passed
@MasterKale MasterKale deleted the tc-clientfeatmethod branch January 10, 2024 20:22
github-actions bot added a commit that referenced this pull request Jan 10, 2024
SHA: 80b478c
Reason: push, by MasterKale

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
webkit-commit-queue pushed a commit to pascoej/WebKit that referenced this pull request Jan 13, 2024
rdar://120442670
https://bugs.webkit.org/show_bug.cgi?id=267068

Reviewed by Brent Fulgham.

The spec has changed such that getClientCapabilities needs to return a record
instead of a maplike. This patch makes that change. For more context, see the
change at w3c/webauthn#1923.

* Source/WebCore/DerivedSources-input.xcfilelist:
* Source/WebCore/DerivedSources-output.xcfilelist:
* Source/WebCore/DerivedSources.make:
* Source/WebCore/Modules/webauthn/AuthenticatorCoordinator.cpp:
(WebCore::AuthenticatorCoordinator::getClientCapabilities const):
* Source/WebCore/Modules/webauthn/AuthenticatorCoordinator.h:
* Source/WebCore/Modules/webauthn/AuthenticatorCoordinatorClient.h:
* Source/WebCore/Modules/webauthn/PublicKeyCredential.cpp:
(WebCore::PublicKeyCredential::getClientCapabilities):
* Source/WebCore/Modules/webauthn/PublicKeyCredential.h:
* Source/WebCore/Modules/webauthn/PublicKeyCredential.idl:
* Source/WebCore/Modules/webauthn/PublicKeyCredentialClientCapabilities.cpp: Removed.
* Source/WebCore/Modules/webauthn/PublicKeyCredentialClientCapabilities.h: Removed.
* Source/WebCore/Modules/webauthn/PublicKeyCredentialClientCapabilities.idl: Removed.
* Source/WebCore/Sources.txt:
* Source/WebCore/WebCore.xcodeproj/project.pbxproj:
* Source/WebKit/Scripts/webkit/messages.py:
(class_template_headers):
* Source/WebKit/UIProcess/WebAuthentication/Cocoa/WebAuthenticatorCoordinatorProxy.mm:
* Source/WebKit/UIProcess/WebAuthentication/WebAuthenticatorCoordinatorProxy.h:
* Source/WebKit/UIProcess/WebAuthentication/WebAuthenticatorCoordinatorProxy.messages.in:

Canonical link: https://commits.webkit.org/272998@main
@lgarron
Copy link
Contributor

lgarron commented Sep 14, 2024

I 100% agree with the camelCase convention reasons, but just a technical note:

const { 'user-verifying-platform-authenticator' } = capabilities;
// SyntaxError: Unexpected token '}'. Expected a ':' prior to a named
// destructuring property.

This is actually possible to do using a : (as suggested by the error message):

const { 'user-verifying-platform-authenticator': userVerifyingPlatformAuthenticator } = capabilities;

However, this is definitely not as ergonomic, more code to ship to the end-user for no good reason, and a less debuggable than if the exact same string is used to refer this the destructured value throughout the JSON and the code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
privacy-tracker Group bringing to attention of Privacy, or tracked by the Privacy Group but not needing response. type:technical
Projects
None yet
Development

Successfully merging this pull request may close these issues.