heartcombo / devise

Flexible authentication solution for Rails with Warden.
http://blog.plataformatec.com.br/tag/devise/
MIT License
23.83k stars 5.54k forks source link

Passkeys support #5527

Open tcannonfodder opened 1 year ago

tcannonfodder commented 1 year ago

Hey there!

With the move towards passkeys (through the FIDO Alliance, Apple's OS updates, Windows Hello, etc.); I think it would be extremely useful for the larger Ruby community to have Devise support passkeys instead of passwords as an authentication method.

After some initial scanning of the Devise & Omniauth wiki, it feels like passkeys support should be baked into Devise itself; because it explicitly replaces passwords. Getting some guidance on how we should move forward would be invaluable.

Passkeys replace passwords (or alternatively: passkeys replace the mechanism of password exchange)

Unlike Shibboleth, SAML, OAuth, etc.; you're not using an external identity to verify a user, the browser & server exchange a public/private keypair instead of asking the user to generate & remember a password. So it doesn't necessarily fit as an Omniauth strategy.

Passkeys replace multi-factor authentication flows (or alternatively: passkeys use MFA by default)

Unlike the multi-factor extensions listed in the wiki, passkeys replace multi-factor authentication extensions because multi-factor authentication is baked into the passkey exchange ceremony (especially with the userVerification: required flag).

Some relevant links:

Passkeys still use a lot of Devise's features

Since passkeys are simply a replacement for the password mechanism, an app that wants to use passkeys would still ideally want to use the 10 modules listed in devise's feature summary (just tailored for registering passkeys instead of passwords).


How would we proceed?

This is a big change, so it obviously wouldn't happen overnight, but I think for the long-term safety of users (both app that use Devise, and the customers of said apps), decoupling passwords out of Devise and replacing them with passkeys is essential.

I think the roadmap for this would be:

  1. soft-decouple passwords out of Devise, replacing it with a generic term like "credential"
  2. Making the credential method Devise uses configurable, defaulting to :password
  3. Adding passkey support
  4. Eventually, making new installations default to using :passkey

Again, getting some guidance on what makes the most sense for Devise would be a huge help. This is a project I believe in, since it raises for floor for everyone, and am happy to help out however I can.

tcannonfodder commented 1 year ago

I've started a fork here: https://github.com/tcannonfodder/devise ; and will start working on PRs to begin the process. I'd definitely appreciate any help from contributors who also want to see this happen

evolve2k commented 1 year ago

Thanks for doing this work.

collimarco commented 1 year ago

Any updates on this?

tcannonfodder commented 1 year ago

Hoping to get back on this soon! Work’s been busy, plus not having any guidance from the maintainers is…demoralizing

rmaspero commented 1 year ago

This is the kind of project I would actually be happy to put money behind to facilitate the upgrade. We use devise in a load of projects and official support for things like passkey and 2FA would be a dream.

collimarco commented 1 year ago

official support for things like passkey and 2FA would be a dream

@rmaspero Yes, that would be great. For 2FA you can use this gem: https://github.com/tinfoil/devise-two-factor

tcannonfodder commented 1 year ago

For those following the progress: I did another batch of renaming existing models & classes to prefix the appropriate ones with Password: https://github.com/tcannonfodder/devise/pull/6

Next up will be working on underlying data models for passkeys

carlosantoniodasilva commented 1 year ago

@tcannonfodder first, my apologies for not getting back here earlier. I've been super behind on all OSS-related things, trying to do some catch up work here again this year.

Second, appreciate the write up and initial work on the passkey support / feature. I can definitely see a future where Devise works out of the box with passkeys, even though I'm not yet super familiar with how they work. (admittedly, that's one area I need to catch up a bit on as well)

I am slightly afraid of renaming all.the.things though, and breaking lots of apps and libraries out there that rely on the current naming & structure of things, for example controllers can be inherited from, etc., so it needs some careful consideration on any renaming. I do have plans to release some breaking changes and major versions in the near future, but maybe one thing at a time. 😅

For now though, I have some other priorities to get through here, initially getting Devise + Turbo working fully out of the box, and then going from there. In the meantime, have you done any passkeys integration with Devise as-is? I'd like to look at (and perhaps, even implement some) integration code myself, before having a better idea of what changes would be necessary to have something like this better baked into devise itself. But like I said, I think it's definitely something that Devise could support entirely in the future.


@rmaspero @collimarco I've had the need to review an OTP/2FA implementation somewhat recently, and have been considering a more official devise + 2fa basic integration, nothing concrete, don't want to overpromise anything yet, but maybe expect to hear more on that in the near future.

collimarco commented 1 year ago

@carlosantoniodasilva Thanks for the great news. For 2FA maybe you could consider a merge of this project into Devise: https://github.com/tinfoil/devise-two-factor It works very well, and it would be nice to have compatibility in the future. The only part that is missing are the views, which maybe Devise could provide.

tcannonfodder commented 1 year ago

An important note about passkeys and OTP/2FA is that passkeys supersede 2-factor authentication methods; because it's baked into the passkey protocol (as mentioned in the documentation as part of the "Passkeys replace multi-factor authentication flows (or alternatively: passkeys use MFA by default)" section).

If there's a roadmap to have Devise support passkeys out of the box, which would involve a migration by apps & libraries, I think the best course of action is should go directly to passkeys rather than implementing OTP/2FA. The reasons behind that thinking are:

  1. It increases the size of devise's codebase, adding an extra featureset that needs to be maintained for a process that's a patch for the inherent insecurity of passwords
  2. Since passkeys replace both password + second factor authentication, if we're upgrading Devise for the next generation of authentication, we don't need to bake OTP/2FA (since it's been replaced)
  3. There are existing libraries to support password + second factor authentication, which can be used in cases where an app/library cannot currently migrate to passkeys for external reasons (which are increasingly becoming less relevant with the propagation of evergreen browsers, mobile devices as roaming authenticators, and security keys)
  4. It muddies the water on the difference between passwords + 2FA and passkeys, causing confusion and potentially guiding an app/library to forgo passkeys (which would be a security loss for both the user & the app/library)

Note: Updating the original list of documentation with an FAQ from the FIDO alliance

tcannonfodder commented 1 year ago

@tcannonfodder first, my apologies for not getting back here earlier. I've been super behind on all OSS-related things, trying to do some catch up work here again this year.

No worries at all, I get it! OSS work is difficult, and takes a lot of time/energy (hence why I also had to take a break) Thanks for getting back to me.

Second, appreciate the write up and initial work on the passkey support / feature. I can definitely see a future where Devise works out of the box with passkeys, even though I'm not yet super familiar with how they work. (admittedly, that's one area I need to catch up a bit on as well)

It's an exciting new feature, I love talking about it! Some helpful links to orient yourself (pulling from the original issue & some of my own research):

I am slightly afraid of renaming all.the.things though, and breaking lots of apps and libraries out there that rely on the current naming & structure of things, for example controllers can be inherited from, etc., so it needs some careful consideration on any renaming. I do have plans to release some breaking changes and major versions in the near future, but maybe one thing at a time. 😅

Yeah, it's a big breaking change. That's why ideally there would be a lot of communication, multiple point-releases where things are marked for deprecation, and a clear & concise upgrade guide. Happy to help out with all of that as well, to ensure a smooth migration.

Unfortunately, I don't really see a way around it. Decoupling passwords out of Devise's core concepts such as authentication, registration, and sanitization is a crucial first step in supporting passkeys. That makes it clear where the separation of concerns lies between:

Since it's already going to require changes from apps/libraries, I think it's better to make Devise as explicit as possible with the separation of concerns, and smooth the migration process with all the ideas mentioned above.

For now though, I have some other priorities to get through here, initially getting Devise + Turbo working fully out of the box, and then going from there. In the meantime, have you done any passkeys integration with Devise as-is? I'd like to look at (and perhaps, even implement some) integration code myself, before having a better idea of what changes would be necessary to have something like this better baked into devise itself. But like I said, I think it's definitely something that Devise could support entirely in the future.

Totally understand, that's important! I don't currently have any passkey integration with Devise as-is. I have implemented a passkeys authentication layer in non-Devise authentication systems, which is what lit the fire in me over getting Devise upgraded to passkeys (so we can all benefit from passkeys!)

Now that I have the first 2 parts of renaming to decouple passwords from Devise, I can start work on the passkeys data models & implementation. Once I start with that, I can make a demo Rails app using the fork, and link to it here; so we can review and refine it! :)

collimarco commented 1 year ago

@tcannonfodder I don't see Passkeys as a replacement for Authenticator apps in the immediate future, I see them as something complementary that can coexist:

  1. Most applications that I use have support for 2FA, but not yet for Passkeys, which is a very recent technology with possibly many unknown challenges. Having said that, I agree that Passkeys are really interesting and probably the future
  2. Passkeys can be used for 2FA. Ideally the user can choose between a 2FA using an Authenticator app or using Passkeys. Heroku does this: https://devcenter.heroku.com/articles/built-in-authenticators#mfa-verification-with-a-built-in-authenticator Basically the Passkeys are the second factor, but don't replace the password.
tcannonfodder commented 1 year ago

Most applications that I use have support for 2FA, but not yet for Passkeys, which is a very recent technology with possibly many unknown challenges. Having said that, I agree that Passkeys are really interesting and probably the future

I agree that there are some challenges that need to be fixed with passkeys currently, but there are workarounds and solutions. A concrete example I've run into is labelling passkeys for different subdomained apps: a solution there is to store the key as a resident key (for a pure passwordless experience), and clearly label the key through the username (eg: App A: email@test.com). This is more a UI problem the browser/OS implementations for keychain access. That's outside of the scope of what app developers have to deal with, since it's part of the larger OS (which we don't have control over).

And the problems are being solved pretty quickly, as far as browser/OS standards bodies go! Especially considering how many big companies are on the board: https://fidoalliance.org/members/

The "Most applications don't have support for passkeys" is a chicken/egg problem, and part of why I've been working to get a first-class implementation in Devise itself. There are a few factors that impact adoption:

  1. Having your authentication library support passkeys (what we're working on now!)
  2. Migrating your authentication library to the passkeys implementation (the documentation, deprecations, and guides we'll need to work on)
  3. Time/effort to audit app code to decouple passwords in your custom code (outside of our control)

Making sure we get a first-class passkeys implementation correct resolves the first 2 points, and having good documentation helps immensely with point 3. It's frustrating, but understandable, that more apps haven't adopted passkeys; but part of that is making sure Devise supports it out of the box with a clear, straightforward implementation. Once that's in place, it becomes a lot more easier to prioritize migrating to passkeys on a roadmap. And, if it's the default for Devise, greenfield apps support get it to start with; driving up the demand for passkey implementations for existing apps. Win, win! 🎉

Passkeys can be used for 2FA. Ideally the user can choose between a 2FA using an Authenticator app or using Passkeys.

This pattern of password + second factor is a bad implementation, caused by slow bureaucracy, miscommunication, and legacy authentication schemes. Technically, using a built-in authenticator (as listed in the Heroku docs), isn't a passkey. It's using the WebAuthn API to use TouchID as a security key. Passkeys are the use of WebAuthn exclusively.

Some notes about this from the passkeys FAQ:

Why are passkeys better than password + second factor? [...] Because the primary factor — the password — is fundamentally broken in multiple ways, the industry has seen widespread adoption of layering on an additional second factor. But unfortunately the most popular forms of second factors — such as one time passwords (OTPs) and phone approvals — are both inconvenient and insecure. They can be phished, and they are being phished at scale today.

Since passkeys are FIDO credentials, we now have a primary factor that — standing alone — is more secure than the combination of either “password + OTP” or “password + phone approval”.

Are passkeys considered multi-factor authentication? Passkeys are kept on a user’s devices (something the user “has”) and — if the RP requests User Verification — can only be exercised by the user with a biometric or PIN (something the user “is” or ”knows”). Thus, authentication with passkeys embodies the core principle of multi-factor security.

RPs may be concerned that a passkey could be made available to an attacker through a single factor (say, a password) from the platform vendor account. In practice, however, this is not usually the case: platform vendors consider multiple signals beyond the user’s password — some visible to the user, some not — when authenticating users and restoring passkeys to their devices.

Note that some regulatory regimes still have to evolve to recognize passkeys as one of the officially listed forms of multi-factor. This is an area of active engagement for the FIDO Alliance.

The migration process is going to be slow, so we should make it as direct & painless as possible

Since we already know that the migration process is going to be long; Devise should be focused on setting up users of the library for long-term success. In my mind, that includes:

  1. Clearly stating that passkeys are the next generation of authentication
  2. Keeping the core implementation focused on passkeys, and providing the tooling for migrating existing installation from passwords to passkeys
  3. Extensive documentation on the migration process, and clear deprecations through semantic versioning

Again, there are other libraries that provide OTP & other MFA methods for Devise. So in my mind, the question becomes one of priorities & maintenance for Devise: Do we take time to implement OTP & other MFA authentication schemes, write up all the documentation for that, deal with the maintenance & issue overhead; and then ask apps/libraries to go through the process of migrating to passkeys? That would certainly slow down the adoption of passkeys industry-wide by making it unclear why someone should do the work on mucking about in their authentication layer twice.

In my mind, it is better to skip the intermediary step and go right to passkeys, since there's already existing solutions for passwords + MFA for Devise through external libraries. It makes greenfield apps better out of the gate, it makes Devise easier to maintain by having a smaller codebase, and it helps clarify what the ideal authentication path is (part of why folks use Devise to start with)

tcannonfodder commented 1 year ago

Started a testbed Rails app to sketch out an implementation; will take me a while as I get used to Devise's codebase (rather than just renaming stuff): https://github.com/tcannonfodder/devise-passkeys-testbed-app/tree/sketch

RE: example implementation for passkeys. Cedarcode (the maintainers for the webauthn gem) have an example app for passwordless login: https://github.com/cedarcode/webauthn-rails-demo-app

tcannonfodder commented 1 year ago

Status update: I've got a experimental, not production ready, very rough sketch, 1,000,000% serious do not use this in production, yes even you implementation in: https://github.com/tcannonfodder/devise-passkeys-testbed-app/tree/sketch

Going to start detangling & refactoring it out into usable bits, but wanted folks to know progress is being made.

tcannonfodder commented 1 year ago

Another status update: I implemented a Warden strategy for WebAuthn, which will be the foundation for any devise implementation of passkeys.

The code is here: https://github.com/ruby-passkeys/warden-webauthn

Note that there isn't any documentation yet, but there are tests and it's got 100% code coverage.

I'm also asking any security researchers to give the code a look, because again, it's a foundational piece of code

Likewise, there is now a GitHub organization for passkey related libraries for Ruby https://github.com/ruby-passkeys

tcannonfodder commented 1 year ago

Some updates:

Maintainers and help are desperately needed for this, see the following list of things where help is needed: https://github.com/ruby-passkeys#help-needed

Again, this code needs security reviews, documentation, and general polish from folks with more devise experience than I do (hence the need for maintainers!)

@carlosantoniodasilva I know you were looking for implementation examples; hopefully these help! :)

brauliomartinezlm commented 1 year ago

Thank you @tcannonfodder for pushing on this initiative. From the beginning and after we got https://github.com/cedarcode/webauthn-ruby to a stable place, we always had the plan of adding a Devise extension that would allow its easy usage for users of such a big and spread out library like Devise.

I think we're making steps in the right direction overall and the challenge will become integrating into Devise without practically redoing many core things it does. From my personal experience, it's gonna be much easier to put something like passkeys as a first and only factor rather than have it as a second factor.

To go back to @carlosantoniodasilva 's question:

In the meantime, have you done any passkeys integration with Devise as-is? I'd like to look at (and perhaps, even implement some) integration code myself, before having a better idea of what changes would be necessary to have something like this better baked into devise itself.

Almost 3 years ago, when the concept of passkeys didn't even exist and it was all just plain WebAuthn, our team spent some time, adding it to Mastodon as an experiment. Being today still the active implementation of WebAuthn or Passkeys as one of the second factor offers in there. https://github.com/mastodon/mastodon/pull/14466 As you'll see, it's a Devise implementation where we plugged in WebAuthn in a kind of artisanal way to accomplish what the featured needed to do.

Hope this helps and looking fwd to stay tuned and add any value I can to this conversation.

tcannonfodder commented 1 year ago

👋 Updating with some news! I've cut an initial alpha of devise-passkeys: https://github.com/ruby-passkeys/devise-passkeys/releases/tag/v0.1.0

Note that this is an alpha build, so it should be used with experimental projects. I wanted to get this version cut as soon as the test coverage was finished so that folks could start providing some concrete feedback.

There's still a long ways to go, but check it out! And, as always, we need maintainers!

tcannonfodder commented 7 months ago

Wanted to assure folks that this project is still alive; but we need maintainers on it!