Closed krnjn closed 2 years ago
Hi @krnjn, thanks for the kind words about the gem!
Integrating it with the JWT gem would be kind of interesting. It looks to me like it will be either fairly straightforward or a complete rewrite 😄. The key in the JWT side of things appears to be this Warden hook in the warden-jwt_auth plugin. Since this gem still signs the user in with warden/devise methods, including the JWT gem may already handle this to some degree.
On the other hand, this gem does use cookies to store data between the sign in and 2FA verification section and I don't know if that would work within a JWT based session. One potential here would be to try to hook into and adjust the scopes that the token has, such that the scope a JWT has after a user has entered their password gives them access to the second factor.
I'd be happy to try to help (though I am currently on holiday, so not spending much time at the computer). Let me know if you've tried integrating the two gems in an application and what the results are so far.
Thanks for the tips @philnash -- I think we're going to try a version that is along the lines of what you're referencing. We'll let you know how it goes and post back here once we figure out some more headway.
I will note that we'll probably need to override a few of the helper / controller methods to handle json?
based requests to avoid using the calls to any cookie
, session
, or redirect_to
calls, but can also share how / what we did and whether those methods can be made slightly more flexible to support this in the future without the custom overrides.
Let me know should you have any other thoughts -- hope you're enjoying the holiday!
Great, I'll be really interested to know how it goes and whether this project can make things easier!
Hey @krnjn did you have any success here?
Even a very brief summary of how (or if!) you got this working would be much appreciated.
Hey @olliebennett -- we ended up rolling much of our own solution but still use both gems pretty heavily for it. I'll try to encapsulate some of the strategy below:
We created a set of api/
controllers that mostly inherited from their devise counterparts (e.g. class DeviseAuthyController < Devise::DeviseAuthyController
and SessionsController < ::Devise::SessionsController
) that handled responding to json requests (e.g. respond_to :json
) and overrode the methods we needed for our implementation. For instance, in our custom API::DeviseAuthyController
, we only overrode the POST_verify_authy
method as that was the use case we had for our sign in flow using JWT (we didn't need to enable the setting of 2FA via JWT, just verifying codes).
We used the allowlist strategy documented here in terms of how to create / revoke valid jwt tokens. Note, if you don't do this then signing in from one device will sign you out from another device (something we learned the hard way) so it was really the only way for us to go. We built on top of this strategy by adding a column of current_sign_in_with_authy_at
on the AllowlistedJwt
table (when it's value is set, the token is "authy authenticated" so to speak).
last_sign_in_with_authy
. This contrasted slightly with the columns generated by Devise of last_sign_in_at
and current_sign_in_at
-- both keeping the current/previous as well as using the Rails convention of _at
for timestamp-based columns. Just noting the divergence since / something we think would be good to bring in sync between the two libraries!On each request our application receives to authenticated routes and includes a Bearer token we check to make sure that the token is "authy authenticated" by checking for a value in the current_sign_in_with_authy_at
column (only, of course, if the user that token belongs also has authy_enabled
-- something controlled via this gem). If not, we return a json
response saying authy authentication is required and have our client-side application prompt the user to enter a code to POST to the custom controller endpoint mentioned above (e.g. POST_verify_authy
). On success of that custom method, their token is updated with the current timestamp and then considered "authy authenticated".
Oh and one thing to ensure is the configuration in the devise initializer so that you only really are activating the creation / destruction of jwt tokens on the json
requests (and can leave your standard server / session based in place):
jwt.dispatch_requests = [['POST', %r{^/api/users/sign_in$}]]
jwt.revocation_requests = [['DELETE', %r{^/api/users/sign_out$}]]
jwt.request_formats = { user: %i[json] }
There are some other interesting parts of this like figuring out how to decode the JWT token manually on each request (this is fairly straightforward, just required reading the docs on this) and it does make you need to really examine how much you're using the session
in your app for other business logic purposes, but the above is more or less the approach we took.
Hope this helps!
Thanks for the thorough follow up @krnjn. I've opened #157 to consider what to do with the last_sign_in_with_authy
column.
This library is no longer actively maintained. The Authy API has been replaced with the Twilio Verify API. Twilio will support the Authy API through November 1, 2022 for SMS/Voice. After this date, we’ll start to deprecate the service for SMS/Voice. Any requests sent to the API after May 1, 2023, will automatically receive an error. Push and TOTP will continue to be supported through July 2023.
Learn more about migrating from Authy to Verify.
Please visit the Twilio Docs for:
Please direct any questions to Twilio Support. Thank you!
Hi -- just want to say that I'm a big fan of the gem and how easy it has been to get up and running with 2FA (especially grateful for the support of Generic authenticator token support given how insecure SMS-based auth can be).
We're currently adding use of JWT-based auth into our application (using the devise-jwt gem) and I was wondering if the maintainers of this gem (or any other users of it) have had experience with integrating Devise + JWT + Authy together as an auth solution. I'm happy to dig into the internals of this gem and try to put something together, but just asking in advance in case someone has some tips / blueprints and I don't have to reinvent the wheel.
Thanks in advance!