davidearl / webauthn

An implementation of webauthn in PHP on the server side (e.g Yubico 2 and Google Titan keys)
https://webauthn.davidearl.uk
MIT License
129 stars 24 forks source link

iphone safari browser is not working #46

Closed nill-developer closed 3 years ago

nill-developer commented 3 years ago

Hello.

Fingerprint authentication is no longer working in the Safari browser on my iPhone. What is the cause and will you fix the software in the future?

davidearl commented 3 years ago

Hi, I wasn't aware Apple had even released this I'm afraid, or that it ever worked!

I will certainly look at this. I can see that it will create the key, with a choice of fingerprint and physical key, but on my phone trying to log in asks for security key rather than fingerprint even though I chose fingerprint for creation. Is that what you see also?

nill-developer commented 3 years ago

Hi It was working about a month ago.

Sorry for the picture on the Japanese version of the iPhone.

Description. I want to authenticate with my iPhone's touch ID, but I get a message asking me to stick in a security key.

I'm trying to get it to authenticate with my iPhone's touch Id, but it keeps asking me, "Would you like to spec out a security key so you can sign in? Please hold the security key close to the top of your iPhone. ↑up Translate the message in the image to English

Translated with www.DeepL.com/Translator (free version) 2021-02-04 181606

davidearl commented 3 years ago

Thanks, that's very helpful. Presumably it was an iOS update that broke it. I'll have a look at it shortly.

davidearl commented 3 years ago

OK, some news:

Firstly, it works for me! At least, it works for me on the example, but when I try a real use case, it does the same as you.

However, I know why (I think)...

I believe it to be a bug/feature in Safari, this one: https://bugs.webkit.org/show_bug.cgi?id=213595 . It looks like it was supposedly fixed in iOS 14, but I don't believe it.

They won't allow fingerprint prompts which aren't initiated by a user interaction, but this is not the usual model for webauthn (as lots of people say in the bug report discussion). When you click the Submit button, the browser has to ask for the challenge for that user, and only when it gets it (in a callback) can it ask the fingerprint request to pop up - which it won't as the callback isn't in a user interaction.

But... it seems to be not strictly in an interaction, but on a timer. I think adding this timer may be their bugfix!

If the callback gets called in a short time, it works. If it takes longer it just asks for a security key, as yours does.

You are in Japan (I assume), and so your request tasks significantly longer than mine to do the round trip to the server. The example is pretty trivial so it responds very quickly, and I am only about 5km from where my server is located, so it responds for me quickly enough that it works.

If I put a setTimeout (of 1000 ms) around the webauthnAuthenticate() call, I can then get it to fail. If I set the timeout to only 1ms it works again, so it's not the setTimeout per-se but the length of time it takes. In fact it appears to work on 700ms but fail on 800ms. So with the server round trip, my guess is they allow a second after the last user interaction.

On my real application, it has to retrieve the credentials from the database, which takes a little longer, and it is on a slower server, so it takes just too long to work there.

I can change the workflow slightly in my real app to engineer it to work, but I'm not sure that's the right thing to do. I think I will respond to that thread, if I am allowed to login, and see what they say.

In the meantime, if you need to get this to work in a real app, what I suggest is to ask for the username on the first webauthn login, and put it in a cookie (from JS) and then fetch the credentials. On receipt, offer another button, and the click handler for that calls webauthnAuthenticate, so it is directly in the user interaction, no delay. On the second and subsequent logins from that device (when you see there is a cookie), deliver the credentials with the page load, or ask for them as soon as it has loaded, and just present the final button as before. That means there is one more click the first time, but after that it is as convenient as before. You'll also need a link to escape from that (clear the cookie) otherwise there's no way out if they can't use the reader form some reason: they need to be able to go back to the password login.

davidearl commented 3 years ago

I'm on iOS 14.4 by the way.

nill-developer commented 3 years ago

Thank you for your research, it's very helpful! My iPhone is also IOS 14.4.

davidearl's PHP program is one of the few that I know of that worked with the iPhone touch ID.

I have incorporated your program in Laravel. The information involving the callback timers is useful.

I'm not sure if you understand English. Is it a matter of processing time from "prepairChallengeForRegistration" to "register" Or is it a problem with the "setTimeout" that calls Ajax in javascript? setTimeout(function () { $('.cdone').hide(300); }, 2000);

davidearl commented 3 years ago

Is it a matter of processing time from "prepairChallengeForRegistration" to "register"

I was looking at the time taken to authenticate, not register. In the example, specifically, the time between pressing the login submit button and JavaScript calling navigator.credentials.get in webauthnAuthenticate. That time includes the round trip to the server in order to call prepareForLogin to get the challenge and certificate.

The same delay effect may well apply in the registration process as that also requires a user interaction, presumably with the same time limit, to allow the fingerprint, and as you say has to do a round trip to the server to call prepareChallengeForRegistration. I was only looking at login, but it would be completely understandable that registration has the same problem.

I was only using setTimout temporarily to increase the time delay artificially so I could demonstrate the same problem you were seeing. (The setTimeout you referred to is not relevant - it just dismisses the "success" or "fail" message after 2 seconds.)

nill-developer commented 3 years ago

Thank you. I understand that there is nothing wrong with the program.

I will implement it in my program.