I am building Rails API app, where I'm using devise-passwordless library.
If I send magic link to user and user waits too long to come to my app, how can I verify that the used token was valid and generate/send new one?
I'm looking for something like:
User.find_by(
email: magic_link_request_params[:email],
old_token: magic_link_request_params[:old_token] )
.send_magic_link(true)
to be sure, that the user was already invited before by authorized admin/manager. If so, I want to be able to resend the magic link, otherwise I don't want to let the user in my app, so looking him up just by email is not enough.
- Ruby 3.2.2
- Rails 7
- ActionController::API
- Devise::Controllers::Rails7ApiMode
- Devise
- Devise-passwordless
By default, Devise isn't saving the token anywhere.
It encodes and decodes the token without any reliance on a User record.
It also doesn't have a method to just generate the token without emailing it, so you're going to be modifying this gem quite a bit to get your behavior.
First, you'll need a migration to add fields to your User object. In my example, I'm using
last_magic_linkandlast_magic_link_sent_atIf you feel comfortable writing your own modules and
prepending them, the cleanest solution is to create an initializer that changes thesend_magic_linkto update yourUserrecord between when the token is generated and when the email is sent:And, the authors did you a favor by adding a method called
after_magic_link_authenticationthat you can use to remove those record attributes from theUseronce they successfully sign in:Now you are saving and removing tokens as they are generated and used.
But you've still got your primary use case: see if the token && email matches if it's expired.
Unfortunately, the gem raises a very broad error:
InvalidOrExpiredTokenErrorand it doesn't tell you which is actually the case: invalid or expired.You can write a similar override on
Devise::Passwordless::LoginToken#decryptand add a message toraise InvalidOrExpiredTokenErrorif the token is expired:Then, in the
Devise::Passwordless::MagicLinksController(that you'll have to ask Devise to generate) you can rescue from that error: