Bug 213595

Summary: [WebAuthn] Use MediaOnly user gesture
Product: WebKit Reporter: ynojima <ynojima>
Component: WebKit Misc.Assignee: Jiewen Tan <jiewen_tan>
Status: RESOLVED FIXED    
Severity: Normal CC: aric, bart.dewater, bfulgham, billylo1, bojan, david, ionescuac, jiewen_tan, levonkarapetyan, matthew, me, Michael.peng, ofirschwartz, paul.mcnamara, peterlohse, teodor.atroshenko, thefatbloke, vganesh, webkit-bug-importer
Priority: P2 Keywords: InRadar
Version: Safari Technology Preview   
Hardware: All   
OS: All   
See Also: https://bugs.webkit.org/show_bug.cgi?id=214444
Bug Depends on:    
Bug Blocks: 181943    
Attachments:
Description Flags
Patch none

Description ynojima 2020-06-25 05:30:47 PDT
Congratulations for Touch ID/Face ID support! I really appreciate your efforts.

I found Safari restricts Touch ID/Face ID usage only within user activated events (Security keys are not restricted).
It seems reasonable regulation at first glance, but it also restricts calling WebAuthn within a callback of promise based asynchronous API.
Here is a pseudo code that Touch ID/Face ID is not available for registration:

```
someElement.onclick = function(){
  var optionsRequest = {
    // set some parameters
  };
  fetch('http://example.com/webauthn/attestation/options', {
      method: "POST",
      body: JSON.stringify(optionsRequest)
    })
  .then(function(response) {
    var options = generateOptionsFromServerResponse(response);
    return navigator.credentials.create(options); // WebAuthn is not directly called by user activated event 
  });
}
```

Calling REST API before navigator.credentials.create.get invocation to fetch options parameters like challenge and credentialId(s) are common pattern in many sites (ex. google.com, webauthn.io), and defined practices in FIDO document [1].
What is the objective of this restriction? I assumed it is a guard for unintended Touch ID/Face ID sensor activation, but there is a dialog asking for user consent before activating the sensors. It seems it's a sufficient guard.

As far as I know, restricting local authenticators to user activated events are not defined in WebAuthn specification. 

I suppose the restriction should be removed, or extend the allowed scope to promise callback chain to improve user experience.

[1] https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-server-v2.0-rd-20180702.html
Comment 1 ynojima 2020-06-25 16:31:53 PDT
I heard mandating user gesture is not scandalized in W3C WebAuthn spec.
https://github.com/w3c/webauthn/issues/1293
Comment 2 Jiewen Tan 2020-06-25 16:35:04 PDT
User activated event is required to use Face ID and Touch ID. This is part of the design of the platform authenticator itself. It has nothing to do with WebAuthn API part.
Comment 3 ynojima 2020-06-25 16:54:16 PDT
I see, but callback of promise chain originally activated by user event should be allowed at least. ( Not sure how hard it is)
As stated before, it restricts fetching option data like challenge and credentialId(s) before neavigator.credentials.create/.get call, which is a common pattern in relying party implementations.
Comment 4 bsimic 2020-06-26 12:14:44 PDT
@Jiewan Tan, if this doesn't get fixed, it will essentially make WebAuthn unusable by any relying parties. The user activated event will still be the providing of Touch ID or Face ID. 

There are zero WebAuthn implementations that currently work using the current implementation because there is a server request required to retrieve the challenge for the options request.
Comment 5 Paul McNamara 2020-06-26 14:10:48 PDT
Likewise it's great to see some real progress with WebAuthn on iOS. 

I agree with the other posters though - this will break our implementation and likely many others.

Would it not make more sense to simply have a native consent UI when "platform" is requested? Confirmation of this would meet the user activated requirement would it not?

If the goal is to prevent nuisance popup UIs then it's currently inconsistent as you still get one if create/get are not called from within a user-activated event handler.

No other browser implementations (including OSX Safari using the touchbar) work this way to my knowledge.
Comment 6 Bart de Water 2020-06-29 11:57:00 PDT
This really confused me as well. I tried Shopify, GitHub and https://github.com/cedarcode/webauthn-rails-demo-app and none of them prompted me to use TouchID as I expected after upgrading to iPadOS 14.

Only https://webauthn.me/debugger works as expected because it's a demo using client-side generated challenges.
Comment 7 Radar WebKit Bug Importer 2020-06-30 11:02:31 PDT
<rdar://problem/64946612>
Comment 8 Jiewen Tan 2020-07-01 17:01:13 PDT
Hi Developers,

Removing the user gesture requirement unfortunately is not an option. The benefit of it is probably not obvious at the current stage. However as you could have imagined, the UI could evolve into a more streamlined version in the future that will certainly be beneficial by this hard requirement.

At the same time, we are considering improvement over the user gesture mechanism, like things in User Activation API that could let the user gesture bit being carried through the promise chain. However, such thing doesn't exist in WebKit yet. Implementing such improvement will take quite some time, and the improvement will probably not be shipped soon. Therefore, I suggest you could prefetch the nonce before the user action as a solution for now.
Comment 9 Fat Bloke 2020-07-06 02:44:22 PDT
Just adding my disappointment to this thread. 

This approach breaks our implementation too.

This will be perceived as Apple going their own way and perverting open standards as they did with the recent OpenId Connect/Sign in with Apple debacle.

https://openid.net/2019/06/27/open-letter-from-the-openid-foundation-to-apple-regarding-sign-in-with-apple/

Please Apple, review this restriction.
Comment 10 Alex 2020-07-07 15:17:42 PDT
I also would like to express my disappointment in this decision. By requiring websites to update their ordering of REST calls leaves a poor user experience when they wonder why they can login into one website with WebAuthn, but not in another, when in reality it should work with both websites.

Why is it not possible to show a consistent dialog, such as Apple Pay which requires user consent to trigger authentication by a new fingerprint touch or double tap to activate Face ID scan and preserve existing behavior?
Comment 11 Jiewen Tan 2020-07-16 21:59:56 PDT
Created attachment 404536 [details]
Patch
Comment 12 Brent Fulgham 2020-07-17 10:58:45 PDT
Comment on attachment 404536 [details]
Patch

r=me
Comment 13 Brent Fulgham 2020-07-17 11:00:08 PDT
For those following along on this radar, we have revised the handling of user gesture to follow the model of our media gesture requirements. This should resolve many of the concerns raised in this thread.

It is helpful to have specific examples of cases where you find the user gesture to be a problem so we can review and see if we can find a path forward that complies with the requirements of our Local Authentication features on our devices.
Comment 14 Alex 2020-07-17 11:13:41 PDT
https://webauthn.io/ is a good website to test with where this bug is triggered.
You can also create a free Okta tenant at https://developer.okta.com and test there as well.
Comment 15 Jiewen Tan 2020-07-17 12:15:44 PDT
This patch should have resolved issues with XHR, but not Fetch API. Bug 214444 is tracking the progress of Fetch API.
Comment 16 Jiewen Tan 2020-07-17 12:16:34 PDT
Comment on attachment 404536 [details]
Patch

Thanks Brent for r+ this patch.
Comment 17 EWS 2020-07-17 12:30:28 PDT
Committed r264528: <https://trac.webkit.org/changeset/264528>

All reviewed patches have been landed. Closing bug and clearing flags on attachment 404536 [details].
Comment 18 Jiewen Tan 2020-07-17 16:41:30 PDT
(In reply to Alex from comment #14)
> https://webauthn.io/ is a good website to test with where this bug is
> triggered.
> You can also create a free Okta tenant at https://developer.okta.com and
> test there as well.

Confirmed this fix works for the first website. For the second one, it's not obvious for me where I could register the platform authenticator.
Comment 19 Alex 2020-07-17 17:27:20 PDT
Honestly if it works with that website you should be good to go, but here is how you would enable it on Okta.

Login to your developer okta tenant with username & password. If you see Developer Console in the top left of the page, click it and select Classic UI to switch. Then under Security --> MultiFactor you can enable WebAuthn as available for your tenant. Afterwards, leave the Okta admin portal and go back to the user profile page. You'll be able to enroll the factor there.
Comment 20 vganesh 2020-07-18 11:01:56 PDT
Which iOS release would this be available in? Will it be in beta3?
Comment 21 Billy Lo 2020-10-05 09:17:40 PDT
(In reply to Jiewen Tan from comment #18)
> (In reply to Alex from comment #14)
> > https://webauthn.io/ is a good website to test with where this bug is
> > triggered.
> > You can also create a free Okta tenant at https://developer.okta.com and
> > test there as well.
> 
> Confirmed this fix works for the first website. For the second one, it's not
> obvious for me where I could register the platform authenticator.

Hi, Jiewen: which iOS/Safari version did you use for verification?  Would like to replicate it for our site too. thx.
Comment 22 Jiewen Tan 2020-10-05 10:56:38 PDT
(In reply to Billy Lo from comment #21)
> (In reply to Jiewen Tan from comment #18)
> > (In reply to Alex from comment #14)
> > > https://webauthn.io/ is a good website to test with where this bug is
> > > triggered.
> > > You can also create a free Okta tenant at https://developer.okta.com and
> > > test there as well.
> > 
> > Confirmed this fix works for the first website. For the second one, it's not
> > obvious for me where I could register the platform authenticator.
> 
> Hi, Jiewen: which iOS/Safari version did you use for verification?  Would
> like to replicate it for our site too. thx.

iOS 14 and macOS Big Sur beta.
Comment 23 Aric 2020-10-25 09:03:35 PDT
Version 14.0 (14610.1.28.1.10)
10.14.6 (18G6032)
MacBook Pro (15-inch, 2019)

As far as I can tell this issue remains. 
Safari always prompts for security key, never Touch ID.

Sites tested:
webauthn.me
webauthn.io
Comment 24 Jiewen Tan 2020-10-26 13:35:45 PDT
(In reply to Aric from comment #23)
> Version 14.0 (14610.1.28.1.10)
> 10.14.6 (18G6032)
> MacBook Pro (15-inch, 2019)
> 
> As far as I can tell this issue remains. 
> Safari always prompts for security key, never Touch ID.
> 
> Sites tested:
> webauthn.me
> webauthn.io

Do you check isUVPAA? Touch ID won't work on any macOS before Big Sur.
Comment 25 Michael 2020-11-03 14:13:47 PST
Hi Jiewen Tan, we've noticed a couple of things:

1) TouchID prompt is not seen when WebAuthn API is invoked as a result of event trigger.
e.g. Button is clicked + API call is made to backend to get challenge and on success we trigger an event. 
Then, event listener calls credentials.create.
Can we fix this? 
2) Today, to avoid many clicks we programatically trigger credentials.get as soon as user lands on the WebAuthn screen in login flow. Chrome allows it on MacOS and doesn't filter TouchID. Is Safari going to fix this to have the same behavior, or do we need to have an explicit click from the user triggering the WebAuthn prompt? Just trying to understand what the direction is, and the degree of user interaction required.

Thank you very much for your time and help!
Comment 26 Jiewen Tan 2020-11-03 14:49:23 PST
(In reply to Michael from comment #25)
> Hi Jiewen Tan, we've noticed a couple of things:
> 
> 1) TouchID prompt is not seen when WebAuthn API is invoked as a result of
> event trigger.
> e.g. Button is clicked + API call is made to backend to get challenge and on
> success we trigger an event. 
> Then, event listener calls credentials.create.
> Can we fix this? 

Can you be more specific? Can you file another bug and upload your test case?

> 2) Today, to avoid many clicks we programatically trigger credentials.get as
> soon as user lands on the WebAuthn screen in login flow. Chrome allows it on
> MacOS and doesn't filter TouchID. Is Safari going to fix this to have the
> same behavior, or do we need to have an explicit click from the user
> triggering the WebAuthn prompt? Just trying to understand what the direction
> is, and the degree of user interaction required.

For us, it's going to require user interactions.

> 
> Thank you very much for your time and help!
Comment 27 Michael 2020-11-04 15:03:14 PST
Hi Jiewan, thanks for your quick reply. Understood on the user interaction requirement. We can create a separate issue later with a js fiddle or similar example implementation to illustrate the callback issue.
Comment 28 Levon Karapetyan 2020-11-09 03:35:07 PST
Hi,

I have noticed that when the geolocation API is used between user click and WebAuthn call, then Safari on iOS 14 restricts Touch ID/Face ID usage. I'd like to know whether this is also a bug or the expected behavior.

Here is a code example:
document.getElementById("mybutton").onclick = function() {
    navigator.geolocation.getCurrentPosition(register, register);
}

function register() {
    var options = {...};
    navigator.credentials.create({
        publicKey: options
    })
    .then(console.log)
    .catch(console.error);
}
Comment 29 Jiewen Tan 2020-11-09 11:54:14 PST
(In reply to Levon Karapetyan from comment #28)
> Hi,
> 
> I have noticed that when the geolocation API is used between user click and
> WebAuthn call, then Safari on iOS 14 restricts Touch ID/Face ID usage. I'd
> like to know whether this is also a bug or the expected behavior.
> 
> Here is a code example:
> document.getElementById("mybutton").onclick = function() {
>     navigator.geolocation.getCurrentPosition(register, register);
> }
> 
> function register() {
>     var options = {...};
>     navigator.credentials.create({
>         publicKey: options
>     })
>     .then(console.log)
>     .catch(console.error);
> }

That's expected behavior. However, I will encourage you to file a bug and cc me.
Comment 30 Aric 2020-11-13 11:32:09 PST
(In reply to Jiewen Tan from comment #24)
> (In reply to Aric from comment #23)
> > Version 14.0 (14610.1.28.1.10)
> > 10.14.6 (18G6032)
> > MacBook Pro (15-inch, 2019)
> > 
> > As far as I can tell this issue remains. 
> > Safari always prompts for security key, never Touch ID.
> > 
> > Sites tested:
> > webauthn.me
> > webauthn.io
> 
> Do you check isUVPAA? Touch ID won't work on any macOS before Big Sur.

Touch ID works fine on these same sites with Mojave+ and Chrome. The published updates indicated this would work in Safari 14, and Safari 14 is now available on Mojave. Can you point me to the documentation that says you must have Big Sur to make this work?
Comment 31 Jiewen Tan 2020-11-13 12:49:42 PST
(In reply to Aric from comment #30)
> (In reply to Jiewen Tan from comment #24)
> > (In reply to Aric from comment #23)
> > > Version 14.0 (14610.1.28.1.10)
> > > 10.14.6 (18G6032)
> > > MacBook Pro (15-inch, 2019)
> > > 
> > > As far as I can tell this issue remains. 
> > > Safari always prompts for security key, never Touch ID.
> > > 
> > > Sites tested:
> > > webauthn.me
> > > webauthn.io
> > 
> > Do you check isUVPAA? Touch ID won't work on any macOS before Big Sur.
> 
> Touch ID works fine on these same sites with Mojave+ and Chrome. The
> published updates indicated this would work in Safari 14, and Safari 14 is
> now available on Mojave. Can you point me to the documentation that says you
> must have Big Sur to make this work?

https://webkit.org/blog/11312/meet-face-id-and-touch-id-for-the-web/
Comment 32 Teodor 2020-12-13 16:49:14 PST
I think there is another common use-case that was missed.

When using WebAuthn as a second factor during authentication, combined with Safari's credential autofill, we get into a situation where the user is automatically trapped by Safari into a failed authentication with application code having very little control.

User opens the login page in Safari, they see the auto-login/autofill card appear on the bottom of the screen. They click on their email address there, which causes the <form> to be automatically submitted. The code that handles form submit then sends a fetch() request that resolves with options for credentials.get call. At this point, it is indistinguishable between form having been submitted via Enter key press, or auto-submitted by auto-fill. So calling credentials.get at this point results in a confusing prompt about inserting the security key, while the user is expecting FaceID to activate.

Yes, hacks can be applied to detect keypresses in form fields, among others. But I think that the correct solution is to treat the user-activated autofill of credentials as a user gesture.
Comment 33 Teodor 2020-12-15 09:26:47 PST
Furthermore, ASWebAuthenticationSession should be exempt from the user gesture requirement. Any native app that relies on web-based authentication (either first- or third-party) that in turn may use WebAuthn, should be trusted in the same manner that calling LAContext with enrolled Face ID results in an immediate scan.

I do think that this issue is not resolved, as expected authentication flows are still not working. But if you think that I should file a separate bug report, let me know.
Comment 34 Jiewen Tan 2020-12-15 11:21:59 PST
(In reply to Teodor from comment #32)
> I think there is another common use-case that was missed.
> 
> When using WebAuthn as a second factor during authentication, combined with
> Safari's credential autofill, we get into a situation where the user is
> automatically trapped by Safari into a failed authentication with
> application code having very little control.
> 
> User opens the login page in Safari, they see the auto-login/autofill card
> appear on the bottom of the screen. They click on their email address there,
> which causes the <form> to be automatically submitted. The code that handles
> form submit then sends a fetch() request that resolves with options for
> credentials.get call. At this point, it is indistinguishable between form
> having been submitted via Enter key press, or auto-submitted by auto-fill.
> So calling credentials.get at this point results in a confusing prompt about
> inserting the security key, while the user is expecting FaceID to activate.
> 
> Yes, hacks can be applied to detect keypresses in form fields, among others.
> But I think that the correct solution is to treat the user-activated
> autofill of credentials as a user gesture.

Thanks for your feedback. Can you file a separate bug on this?
Comment 35 David Earl 2021-02-04 11:13:37 PST
I have been trying this on my test example (at https://webauthn.savesnine.info ) on iPad OS 14.4 and on iOS 14.4, in response to a user bug report re the code that implements that example, that webauthn with iPhone fingerprint had stopped working.

It works for me - in the example. But it fails in the same way as the bug they submitted in a real world app.

The difference is they are in Japan and I am in the UK, only a few km from my server. Their server round trip (and also the server response in a real app) is significantly longer than me using the example.

I tried adding a setTimeout so the workflow becomes click - fetch credentials/challenge - receive them in callabck - wait for setTimeout callback - ask for authentication in setTimeout callback

This still works for me in the example case for timeouts below 700ms. At 800ms it fails. It looks to me like there is a timer after the last user interaction during which the authentication request is allowed (probably 1s - my 800ms artificial delay + server roundtrip, seems plausible). My Japanese correspondent has a much longer round trip time (and you may or may not also if you try the example).

I am not clear from the discussion here what release version of iOS this is fixed in, and whether that time delay IS the fix or is what it was before the fix this bug refers to and the real fix hasn't got into a public release yet. If 14.4 should work, then it doesn't, at least not when there is a sufficient delay between pressing the button and asking for authentication.

When it works for me (short roundtrip) I get one iOS box pop up which asks which method I'd like to use, I reply fingerprint, and then I get the fingerprint prompt. When it fails, I just get an invitation to plug in my security key. The only difference is the length of time it takes to ask the server for the challenge/credentials.