jeremyevans / rodauth

Ruby's Most Advanced Authentication Framework
http://rodauth.jeremyevans.net
MIT License
1.65k stars 95 forks source link

Add WebAuthn Autofill feature #328

Closed janko closed 1 year ago

janko commented 1 year ago

This feature activates the autofill UI for passkeys that the browser has stored on the login page, allowing the user to skip entering their email address or submitting the login form. It implements the same UX seen in https://webauthn.io/.

It builds on top of the WebAuthn Login feature, reusing its login endpoint. It ships with separate JavaScript that activates the autofill UI on the login page, and automatically submits the WebAuthn login form when the user selects their passkey. The WebAuthn login route then retrieves the user from the WebAuthn credential ID.

This requires requires the autocomplete attribute on login field to include webauthn. We now need to generate WebAuthn options without first identifying the account, so we handle that. The passkeys.dev website recommends asking for user verification preferred and discoverable credentials, so we change that as well.

The WebAuthn autofill form is mostly the same as the WebAuthn auth form, except that it loads different JS, doesn't have the login field, and hides the submit button to make the form invisible, since it's meant to be submitted internally by the JS.

https://user-images.githubusercontent.com/795488/228453630-318a8bed-4e36-4802-be52-180059077811.mp4

janko commented 1 year ago

Yes, you should be able to test the autofill in any desktop browser that supports passkeys, such as Chrome. I've updated the PR description to include a video of me trying this out in rodauth-demo-rails. I'll also add it to the demo site 👍🏻

The only hiccup I experienced is that the autofill UI sometimes opens only on 2nd focus to the login field, though I think that might be Turbo-related, because it only happens when I was previously on another page.

janko commented 1 year ago

I extracted the requested methods, and added the feature to the demo site. I also handled the credential not being found in account_from_webauthn_login, which can happen if the browser has a passkey stored that was deleted from the database.

jeremyevans commented 1 year ago

Yes, you should be able to test the autofill in any desktop browser that supports passkeys, such as Chrome. I've updated the PR description to include a video of me trying this out in rodauth-demo-rails. I'll also add it to the demo site 👍🏻

I tested this and that was not my experience. I can get webauthn working in both Chrome and Firefox on Windows 10, but I could not get the autofill working in either browser.

https://webauthn-conditional-ui-demo.glitch.me/ shows this for Firefox: No platform authenticator found. If your OS does not come with one, try using devtools to set one up. And this for Chrome: Conditional UI is understood by your browser but not available. If I go to chrome://settings/passkeys, I see To manage passkeys, use a newer version of Windows. So by default, this doesn't work for Chrome/Windows 10 (probably the most common browser/OS combination currently).

I could work around this during testing by going to the WebAuthn tab in Chrome DevTools, and emulating an internal authenticator that supports resident keys and user verification. Then it does work correctly.

I don't have a problems shipping this, since I assume support will get there eventually. And if conditional mediation isn't supported, everything else appears to still work.

Automated testing looks fine. I hope to have this merged today. Thank you very much for working on this.

jeremyevans commented 1 year ago

@janko Is it possible for you to add covering specs for this? I'm seeing 6 uncovered lines and 4 uncovered branches in webauthn_autofill. If not, I can probably do so, but as you have more knowledge and experience in this area, I think it would be better if you handled it.

janko commented 1 year ago

Thanks for testing it out. I didn't realize there were OS requirements in addition to browser, but it makes sense. Yeah, the idea was that just nothing happens when conditional mediation is unavailable, and you'd still be able to use standard WebAuthn login flow.

Sure thing, I'll try fixing test coverage tomorrow 👍🏻

janko commented 1 year ago

Got to 100% of line & branch coverage, and tested the autocomplete attribute on the login field. That was pretty useful, because during this I uncovered and fixed two more bugs: