#97 PASSWORDLESS login with MAGIC LINK

16/11/2022
authentication
  • Demo how magic link login works
  • install gem Passwordless
  • sign in flow, find or create user
  • missing host to link to
  • click on link to sign in, link to sign in and sign out
  • fix Turbo errors
  • customize magic link Email
  • gem letter_opener to preview emails
  • authorization - only logged in users can edit posts
  • Summary

Passwordless authentication via magic link is an interesting alternative to email-password authentication solutions like Devise.

A passwordless authentication flow looks like this:

  1. Enter your email address
  2. Receive login link in an email
  3. Click link = You are logged in!

I’ve implemented passwordless authentication in insta2blog.com, and for now I am super happy with the solution πŸš€. Feel free to try it out!

In a way this is a more secure authenication strategy, because there is no compromised password point of failure. It is as secure as your email account.

However to even start using this solution in production, you will need to set up sending emails in production.

It is not hard to create this kind of authentication solution on your own, however I prefer not to reinvent the wheel. Gem passwordless neatly solves the problem.

Episode source code: https://github.com/corsego/97-passwordless-auth/commit/34517fddf74572308780c1593ef23c0c85872301
Text version: https://blog.corsego.com/gem-passwordless-magic-link-auth

0:00 Demo how magic link login works
1:29 install gem Passwordless
7:05 sign in flow, find or create user
9:13 missing host to link to
11:30 click on link to sign in, link to sign in and sign out
13:50 fix Turbo errors
16:11 customize magic link Email
17:11 gem letter_opener to preview emails
18:27 authorization - only logged in users can edit posts
19:47 Summary

Transcript
Hello. Recently I was building a hobby project and at some point I needed to let users log into the application, so I needed to add an authentication solution, and I didn't really feel like adding device because it felt like a cumbersome, uh, heavy solution to add to this simple lightweight application. So I wanted to use a more simple authentication strategy, and what came to my mind was to use, uh, magic link authentication. This is how it works. I would click some kind of log in, uh, uh, link and be redirected to a form that I need to input my email. So this is a form for a magic link, uh, authentication. I would be able to input my email address and click send magic link. After this, I would receive an, uh, email with a link to sign in. Here's the email, so the magic link and, uh, it says, please confirm and here is your link to sign in. It'll expire in, uh, an hour, and this is the device that you use to sign in. So if I click this link, here I am, I'm signed in and I can see this user specific data. I can see my username, I see the logo button. So you see, all I did was input my email address, receive an email and log in. That's it. And this way you don't have to store passwords and uh, this solution is as secure as your email account. But if somebody managed to break into your email account, I think you've got bigger problems to worry about. So we are going to implement this kind of magic link password list authentication in this episode, and we're not going to build it from zero. We are going to to use a nice solution named Passwordless. So it is a cool gem to add this kind of, uh, magic link authentication. So let's add it to a new rails application. Now here I've already generated a new Rails application and it has a list of posts. So, uh, if I start the server, I have, uh, I have created a new application and I can add posts. And I want to make it so that the uh, posts are tied to users and only a user who has value created the post can edit the post or delete this post, and only an authenticated user can create the post. So I would need to add current user and all this kind of authentication. So let's go through this gem. I will add the gem passwordless. So I will say, um, bundle add passwordless. This way I don't have to manually edit to the gem file and run bundle because I, uh, injected it into the gem file using this bundle add, and it automatically, uh, run the bundle command. Now I can run the migrations for passwordless. Let's do this. So let's see this migration file that was just generated. I'm going to DBM migrate, and here's this migration. So here we're going to keep all our authentication tokens, all these, uh, kind of, uh, magic link, uh, uh, information. So we know here's the magic link. The remote attributes, uh, is, uh, the IP address. This is the user agent. So, uh, that it was like Mozilla, Macintosh and so on, uh, claimed at time out, add and expired at. So how long until, uh, the authentication expires, how long until the token is made invalid. So quite, uh, basic information that you'll need to make this tokens valid and make them work. Okay, let's go further. We need to generate a current user. So we need a user table. And, uh, in our user model, we will need only an email. Now remember, when you create a a device user, that will be a real lot of information, but here, well, we need just some kind of unique idea. That is the email to which we're going to send the, the authentication links. So we are going to generate a user model with an email. So here is the migration. We just need an email for the user. And you can also say, null false. Well, you, you don't want to have a user without an email. Okay, now we will run the migrations rails. Did we migrate? And, uh, let's go back. So, uh, suggests, uh, us to go to the user model and to add this line passwordless with email to, well let the passwordless module know, uh, that we're using this model. They use model for authentication and we are using it, uh, to authenticate by the email attribute. So let's go to our user rb, and here we're going to add passwordless with email. And we are also going to add these validations. Now, part of these validations, I would also suggest adding validate in the format so that, uh, the red of the email, uh, does look like an email. I will say also format equals with URI mail tool. Re email re let's have a look at what this, uh, looks like in the console. I will start the rails console right in the strateg. And you see this is a project that you don't have to come up with, uh, on your own because it already exists. So this will improve the email validation. And let's continue. So we will need to add the passwordless rules for our users. I will go to our rules rb and uh, I guess on the top I'll add this Passwordless for users. Let's have a look at the route that have been generated. I will start the server and go to local host rails in info route. And here on the very bottom, we have this route for Passwordless engine. So it has created four new route that sign out, that sign in, uh, the submission of the form and the, the submission of, uh, the token sign in path. Okay, now let's, uh, continue. So also in the application controller, we're going to add the password list. Let's go to our application Controller. And here we're going to add a couple of methods. So, uh, the first method, the helper method, current user. This one is going to find our current user by the session. And the second method required user will be used to ensure that only an authenticating user can go to the new action, to the edit action, create, update, and destroy the post. So only authenticated users will be able to like, uh, do actions with posts, but uh, uh, authenticated users will be able to see posts. Only see posts. So, okay, let's see what else we have in this. Uh, uh, read. Nothing, uh, important at this moment. I guess let's already try using this parts for less authentication. I will, uh, oh, maybe start the server and go to our application. Okay, so I want to sign in somehow. Uh, how can I sign in? Let's have a look at the route. So it would be slash sign signin. Okay, no route matches, signin. I'll maybe go to users slash signin. And here is this route. Now I don't want this route to be slash users. I will make it just slash signin. So we also have this in our, we would update our route to be, not passwordless for user, but passwordless for users at the bare URL. So let's go back. I will, uh, refresh this, uh, send in page and I don't need the slash users anymore. Okay, it looks better. So let us try to send in, I will input and email address and it says, well, if you found the email, they'll have sent you an email. Looks good. Let's have a look at the, uh, logs. They have this email anywhere. So, uh, actually no. So no email has been sent. Let's also ensure that there was no passwordless session created. I will start the new console. So let's start the new console, passwordless session.com. So no session has been created because uh, we have to explicitly tell passwordless that, that we want to be able to create new users. Then, uh, searching for an email. So we have to either find or create by the email, not just find, because by default, passwordless would work as kind of invite only and uh, it wouldn't create new users if they're not found by this email. So again, in the rhythm, need to find this FE resource for passwordless and we will say find or create by email. This way we are actually going to create our users. So we are going back to our user rb and we are going to say, uh, find upgrade by the email. Now we don't need this email application because, uh, well it is the same. So, uh, let's go back. I will try to sign in. I will use the same email. I click send magic link and already you have a different problem. So missing host link to, let's have a look at our console. So here we went to the mailer. We tried to send a magic link email and we got an error. The error says that, uh, it's missing a host link to. So we didn't manage the process, send in an email. Okay, so it already tried to send an email, it's already some progress. Let's have a look in the console. So the first Passwordless, uh, session has been created. Uh, the token is filtered for security visit the user agent, the, uh, local host, uh, address, user ID one. Okay, so we created a possible session and we've also actually like created a user. So user first, yeah, the user with this email has been created. Okay? So we need to now set up sending emails in our development environment. So I will go to development rb and here I'll need to add, uh, a few lines. So I will say, uh, config action mailer default, UL options Equals host will be local host and port will be 3000. And I'll need to add a few additional grams. So action mailer delivery method, let's say SMTP and uh, form deliveries. Let's say a form deliveries equals, let's say false. And we will say, uh, raise delivery errors put true. Okay, so, uh, let's see if uh, this actually works. Let's say be dev, we were starting to sell because we have changed something in our environment settings. I will click send magic link and it and then happen. Yeah, so an email has been sent and if you don't have kind of an error in the console, but you see this form hasn't updated, we didn't go to the like successful submission page. So there is still a problem to solve. But here we have our magic link. So let's try and use this magic link. So I will just, well click this magic link, I will copy it and add it to my browser. And didn't happen, I dunno. We might be logged in. We might not be logged in. Let's see if we have a current user, let's go to our application HTML and try to find the current user. Let's say, um, equals current user, Okay? Uh, dot email. So yes, we are actually authenticated. There is a current user looks good. So let's add some, uh, code to be able to, uh, log out. So, uh, we have our current user email and we'll say link tool and we will add some kind of logout path. Uh, what is the route for logging out? Let's see. It is sign out path. So we would say, um, uh, sign out and I think it'll be out dot sign out off. And uh, we would actually need to make it not a link to button bottom. And we would say method would be delete. Let's see if it works. I'm going back. Uh, okay, some kind of syntax error. Yeah, Mr. Coma. So, uh, it would, is not sign up, but uh, sign out. Okay, let's try click sign out and no method, current user email. So if we did manage to sign out, let's update this links. Let's say, uh, if current user dot present, then they're going to show the email and the send out link. Let's see. Okay, it works. So now we'll also need to show a link to sign in if there is no current user. So we'll add an L statement, so else, and we'll have a link two Equals link two sign in and it'll be al sign in path. Now we need this alt to specify that they're using password plus for these uh, URLs. So okay, we have the sign in and it kind of works. So we've added our basic, uh, user standard navigation. And uh, now we need to fix this problem. You see, I will input an email and this phone doesn't redirect, so I kind of, uh, submit, but it doesn't redirect because we need to make Turbo work correctly with this jump. So I will need to generate the views and let's see how we can do it. So we will say rails generate passwordless views and uh, there are actually really few views. So in device and order like tive views I guess. And here we have all the three templates. Let's have a look at these templates. So, uh, I'm going to passwordless in our views and we have our mailer. So this is the text of our mailer. We can customize it the way we want. We can add the session details. For example, I'll say add session attributes. Then we have our sessions new and sessions create. I don't know why this was, uh, not created in the sessions folder in my case. I will, uh, I will move it to the correct folder now. So the correct action is going into the sessions folder. Okay, and what do we have here? So here we have, uh, uh, a text that, uh, I have successfully, uh, submitted the form and here is the form itself. So for my comfort, I will update it from form, photo form with no doubt. Session here has the URL, and we will importantly say data, Uh, turbo false. Okay? And we can also add some additional, uh, uh, frontend validation for our email field to require the email before submitting the form because, uh, I think if, uh, we don't require the email in this form, let's try to submit it. Um, yeah, so we can actually submit the form without an email and it should not kind of, uh, be possible. So let's say via required True, okay, now I can submit the form without having an email so it looks better. And let me now try to submit the form. So I will take one of these emails, click submit. And you see now as I have disabled turbo on this form, it does correctly redirect me to the next page. So it looks quite well we have, uh, updated this form and let's actually have a look at the email. So here is the email, and you see we've also added this session data. Now, you don't want to send all this kind of session data in the email, but you might want to add something. So here we have our session user agent, uh, the time the token was created. So, uh, we can say something like, you might have tried to log in from this device and we will have the session user agent and regards your app team. So this way would, would, uh, say that like there was a login from some kind of specific device. Let's, uh, try this email. Once again, here's the email. So here's the link and here's, you might have tried to log in from this device and the information about this device. Okay, looks good. Now it is kind of, uh, naturally nice to look at our emails in our consultative. Want a better way to preview our emails. So a good way to develop emails in a developing environment would be with the gem, uh, letter opener. Yeah, this one. So here's the gem and let's add this gem to our application. So I'm going to our GEM file development group, and here I will add the gem letter opener. Now I'm running the bundle And to make letter open up work, I'm going to say delivery method lab, the opener in our development environment. So not as MTP and perform deliveries through like this. Now let me stop the server. Once cancel, I will restart it bin do. And let me once again try, uh, signing in. So here I will try to send a magic link. And you see in a new tab, it actually opened the email. So here we can have a good preview of what our emails look like. And uh, we can click the magic link without going to console. So I cleared the magic link and you see I'm signed in. Okay, now let's actually work on, uh, some, uh, authorization. So only users that are logged in can go to create. And you post, I will now sign out and I will add this action we've added to our application controller. This one require user to outpost controller. So I will say before action require user and only for a few actions. So only let's start with, uh, new, let's see if it works. I'm going back. You see, whenever I click new, it directs me to the root path, but I can still go to all other paths. So let's require a user for all the actions except the index and show. So require user only, let's say, um, we have, uh, new added create date and destroy. Okay? Now, whenever I try to not just view a post, but uh, edit it or go to new or destroy a post, I have to be signed in. So let's try signing in And now I can access all these actions. So this way you can, uh, require a user to be authenticated to access specific actions in your controllers. And, uh, I think that's, uh, basically it. So, uh, there isn't really much to it. Just you must also remember that you should, uh, also enable, send an actual real emails in your production environment before going further with this, uh, authentication method because, uh, uh, if, uh, well you only have, uh, emails sent in your development environment, uh, you won't be able to actually authenticate any users except, uh, if you see the emails in your console in the production environment. So I encourage you to have a look at my other videos where we set up sending emails in production using AWS Simple Email service. So I guess that's it, uh, and see you in the next one.
1
Join the conversation
Sign in to access PRO lessons, access private repos, leave comments, create watch lists.
We collect your email address, name and username to create your account. We do not share your email address with anyone else.