Password-less Login with Laravel 8+
Create a pass-phrase or 'magic-link' login system for Laravel 8 and Jetstream
โ
Checked works with Laravel 10
Introduction
Users don't manage passwords well. They forget them or choose easy to remember passwords then use that same password on every site they visit. We then have to build features into our application to let them login when the password is forgotten or allow them to change the password at any time. We have to ensure we hold passwords securely even if our application is not that important because a user might be trusting us with their use-everywhere password.
Our applications are easier to manage with less support issues if we use a password-less login process. Such schemes are popular on sites like Medium with their 'magic link' or Notion.so with their login code such as jay-bawl-sack-lid
There is a good discussion of these password-less login methods in this article on medium.
So, how could we implement such a solution with a new Laravel 8 Jetstream project?
This article is using Jetstream with Livewire (this site is TALL stack focussed) but the principles should hold for Inertia also.
Prepare
I'm starting with a new Laravel 8 project, with Jetstream installed in Livewire flavour.
Remove the requirement for passwords
Our first task is to remove passwords from the Login process. To make this simple, we will give everyone a default password of 'password'. It won't be used, but it prevents us having to make too many changes to the Fortify+Jetstream code.
Remove password fields from login and register forms
Note the additional line for creating a hidden password field with the value 'password'
Create user with default password
Fortify actions are available in the app/Actions/Fortify folder. We can adjust the CreateNewUser.php file to use our default password.
Here, the password field is commented out of the validation, and the password added to the user record is just the hash of the string 'password'.
Testing: We should now be able to register a user, and login without any password. We have built a very insecure application at this point!
Article sponsored by SixTokens.com
Create a source of pass-phrases
For this solution a passphrase is a combination of 3 or 4 words separated by hyphens. The words are sourced from a list published by the EFF and are chosen because they are short and easy to spell.
Rather than publish the code and word list here, you can access it via https://github.com/snapey/passphrase
Create a folder within app called
Utility
Place PassPhrase.php and wordlist.txt in this folder
Put pass-phrase into session when user logs in
When the user registers, Laravel will fire a Registered
event and when the user is logged in either by the login form or the remember function, then a Login
event is fired. We are going to listen for these events and place our randomly generated pass-phrase into session. Ultimately, the user will only be allowed access to our application if they can provide the same code that we have in session.
Create a listener
php artisan make:listener RequirePassPhrase
This uses our utility class to create a pass-phrase and store it along with an expiry timestamp.
With this; $this->generator->passPhrase(3);
we create a phrase with three words. Set this according to your preferences. The EFF article mentioned earlier explains;
for k words chosen from a list of length n, there are nk possible passphrases of this type. It will take an adversary about nk/2 guesses on average to crack this passphrase.
Our wordlist is approximately 4100 words, so 3 words is 4100x4100x4100/2 = 34,400,000,000 guesses so don't go overboard with the number of words in the passphrase.
Bind our Listener to Events
Listening for events is configured in the app\Providers\EventServiceProvider. We add our listener for the two events. We can remove the email verification listener as if the user can receive the passcode then they verified the email at the same time.
Send the pass-phrase to our user
Having created the code and put it in session we need to send this to the user. Notifications are the easiest to implement here, and you could, for instance, choose to send the code to the user by one of the other notification methods such as SMS text.
Create the notification
php artisan make:notification AdvisePassPhrase
Adjust your new notification (use your own words as required)
Call the Notification and provide the pass-phrase
In our earlier Listener, add a line to send the notification;
Line 7 is added to the earlier file
Testing
Provided we have configured a mail service such as mailtrap, when we register or login, a mail should be received containing our passphrase.
Accept and validate the pass-phrase
Create Controller
php artisan make:controller PassPhraseController
Call this from your routes file
Create a form for the capture of the pass-phrase
The easiest route with a new application is to just copy the Login view and edit a few of the fields;
Testing
Visit the route
/login/confirm
and check you can see the form.Entering an invalid code should show the message that the code is incorrect
Entering a valid code should direct to the home route
Waiting 15 minutes and entering a code should report that the code has expired
Add middleware to block access until pass-phrase accepted
We can create a middleware that checks the user's session. If it contains a passphase
key then the user is in the middle of logging in and should not be permitted to access the application. We need to except the login routes from the middleware so that the user can access the login process.
Make Middleware
php artisan make:middleware PassPhraseGuard
Include the Middleware as a global route middleware
Include the new middleware in your web middleware stack
We added Line 10
Testing
Once logged in, access to all pages should be blocked, directing the user to the confirm pass-phrase page.
Landing on the site after 15 minutes should return to the guest mode
Remember me should work as normal
Cleaning up
Remove references to passwords
In the config/fortify.php file, turn off the ability to reset and change passwords by commenting out the resetPasswords
and updatePasswords
features.
Conclusion
In this article we created a password-less login process that uses a pass-phrase technique. In the second part of this article we will add a magic-link alternative.
Feedback
If you have any suggestions how this article can be improved, contribute to discussion at https://github.com/snapey/talltips/discussions
Last updated