MasterKale / SimpleWebAuthn

WebAuthn, Simplified. A collection of TypeScript-first libraries for simpler WebAuthn integration. Supports modern browsers, Node, Deno, and more.
https://simplewebauthn.dev
MIT License
1.63k stars 137 forks source link

iOS Safari fails on conditional UI on page refresh #348

Closed rmmeans closed 1 year ago

rmmeans commented 1 year ago

Hi!

I think my issue may be a Safari bug itself. I am currently working on implementing passkey support with conditional mediation for a product I support and had not used your library yet (looks nice!! πŸ‘€). I found your implementation when testing our own and I was able to reproduce an issue we are having with your implementation directly on your example.simplewebauthn.dev site.

Here's what I'm doing, I wanted to see if you are seeing a similar experience.

The following steps need to be done on an iOS device running iOS 16 (I'm running iPadOS 16.2 in the following example)

  1. Load https://example.simplewebauthn.dev/ in Safari
  2. Attach remote debugger via Safari on mac (not necessary, but useful to see error)
  3. After registering, reload the page so you get the conditional UI login.
  4. This page load should work, and you should be able to login via conditional UI. HOWEVER, refresh one more time instead of logging in.
  5. Your console should see an error of NotAllowedError: User clicked cancel, or the authentication ceremony timed out

If you get the error in step 5, the conditional mediation promise died. This is odd however, because it happened immediately on the page refresh. If you refresh a second time, you will have no console error and can proceed with login. Refresh a third time, and you are back to the error again in your console (and unable to login). Curious if you have any thoughts on this? Seems like a safari bug on iOS. I haven't been able to reproduce on Safari on Mac.

MasterKale commented 1 year ago

What a coincidence, I was just dealing with this today! I ended up logging a WebKit bug about this behavior in iOS Safari because even in a stripped down example, sans any libraries, the error was reproducible:

https://bugs.webkit.org/show_bug.cgi?id=251817

5. Your console should see an error of NotAllowedError: User clicked cancel, or the authentication ceremony timed out

This specific error message is coming from my library, here:

https://github.com/MasterKale/SimpleWebAuthn/blob/master/packages/browser/src/helpers/identifyAuthenticationError.ts#L38

FYI the raw error message being raised by iOS Safari (that I'm inadvertently overwriting here in identifyAuthenticationError()) is actually "Operation failed", if that helps you detect and handle it.

There's this bit of extra info I left as a comment in the bug that might interest you, from a bug behavior perspective:

Experimenting a bit more, if I actually go through with the conditional UI prompt on each page load the subsequent page reload works fine - no error, conditional UI promise resolves with a response from .get(). But if I reload the page multiple times then I start to see this alternating pattern of "error -> no error -> error -> no error..."

And when I don't see the NotAllowedError in the console on page load, conditional UI resolves as expected after selecting a passkey. When I reload the page and see NotAllowedError, an attempt to complete conditional UI neither resolves nor rejects the promise (I guess because the NotAllowedError already rejected the promise?)

rmmeans commented 1 year ago

What are the chances! πŸ˜‚ I am just thrilled to know that I'm not crazy, debugging iOS bugs locally when dealing with Webauthn and the TLS requirements to get browsers to operate with it is not exactly fun.

This is slightly unrelated, but another annoyance I am having right now is it's really difficult to figure out how to deal with the NotAllowedError: Operation failed error. It seems console.log is the most we can do because on a conditional UI this error is also thrown on an iOS device if the device is not enrolled in Face / Touch ID. The PublicKeyCredential.isConditionalMediationAvailable() passes as expected since the browser supports that feature, but then the device is not setup to issue keys since the user has not enrolled in the device's biometric authenticator.

I replicated this yesterday in a iOS simulator. Once the simulator spins up, use your example you filed in the ticket, and hit that page via the simulator. In the simulator app menu if you go to Features > Face ID > Enrolled and make sure enrolled is not checked, then every page load will fail with NotAllowedError: Operation failed.

I guess the long story of this is we have to silently ignore Operation Failed errors. We just need apple to fix the bug you filed so that our apps actually work all the time for logging in instead of silently failing with no error message if they reload the page.

MasterKale commented 1 year ago

The PublicKeyCredential.isConditionalMediationAvailable() passes as expected since the browser supports that feature, but then the device is not setup to issue keys since the user has not enrolled in the device's biometric authenticator.

So you don't even get a chance to use a passkey on another mobile device? Or even a discoverable credential on a security key? The conditional UI simply, immediately errors out? That's disappointing :(

I guess the long story of this is we have to silently ignore Operation Failed errors. We just need apple to fix the bug you filed so that our apps actually work all the time for logging in instead of silently failing with no error message if they reload the page.

Yes, this is where I'm at too. It's probably the exception that an iPhone hasn't had a biometric enrolled so hopefully that particular instance of "Operation failed" will be few and far between. But that alternating conditional UI error definitely needs to get fixed sooner than later.

rmmeans commented 1 year ago

So you don't even get a chance to use a passkey on another mobile device? Or even a discoverable credential on a security key? The conditional UI simply, immediately errors out? That's disappointing :(

Yes - painful! πŸ€• This is what I get on your example.simplewebauthn.dev site in the iOS simulator when FaceID feature is not enrolled. Every single page load results in an error in the conditional UI call. The error goes away and becomes just every-other request (the bug you filed to webkit) when FaceID feature is enrolled.

image

MasterKale commented 1 year ago

@rmmeans I'm going to convert this issue into a discussion since it's reporting a problem with a platform instead of this project. I think it's worth trying to keep this discoverable, though, and a discussion seems like a better place than the Closed section of Issues.