Laravel magic link login
Introduction
Passwordless authentication is an alternative to traditional password-based authentication, which can be difficult for users to remember and potentially vulnerable to hacking attempts. Instead of requiring users to enter a password, passwordless authentication uses other factors to verify a user’s identity, such as email links or biometric authentication.
In this tutorial, we’ll be using Laravel/PHP to create a passwordless authentication system that sends a unique link to a user’s email. When the user clicks the link, they’ll be logged into their account without needing to enter a password.
Creating a New Laravel Project
To get started, we’ll create a new Laravel project using Composer. Open up your command line and execute the following code:
composer create-project laravel/laravel passwordless-app
cd passwordless-app
This will create a new Laravel project and navigate you to the project directory.
Configuring the Database
Next, we’ll configure the database by editing the `.env` file to match our database settings. You can create a new database using your preferred database client; in this example, we’ll be using MariaDB and HeidiSql as our client.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=Enter_Your_Database_Name
DB_USERNAME=Enter_Your_Database_Username
DB_PASSWORD=Enter_Your_Database_Passwordl
Removing the Password Field
Since we won’t be using passwords for this project, we’ll remove the password field from the user model. To do this, modify the `2014_10_12_000000_create_users_table` migration to exclude the password field:
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->rememberToken();
$table->timestamps();
});
}
Additionally, we can drop the password resets table by removing the `2014_10_12_100000_create_password_reset_tokens_table.php` file:
rm database/migrations/2014_10_12_100000_create_password_reset_tokens_table.php
Finally, run the migrations to apply the changes to the database:
php artisan migrate
Creating the Passwordless Authentication System:
Now that we have set up the database, we can start building our passwordless authentication system. Our goal is to send a unique link to a user’s email that, when clicked, will log them into their account without requiring a password.
To accomplish this, we’ll need to add a new column to the users table to store the unique login token:
Schema::table('users', function (Blueprint $table) {
$table->string('login_token')->nullable()->unique();
$table->timestamp('login_token_created_at')->nullable();
});
Before proceeding with logic we need to prepare laravel Mailable class which we will use to send emails.
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class MagicLinkEmail extends Mailable
{
use Queueable, SerializesModels;
public $magicLink;
/**
* Create a new message instance.
*
* @param string $magicLink
*/
public function __construct(string $magicLink)
{
$this->magicLink = $magicLink;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->markdown('emails.magic-link')
->subject('Your Magic Link')
->with([
'magicLink' => $this->magicLink,
]);
}
}
And the email template
@component('mail::message')
# Your Magic Link
Click the button below to log in to your account:
@component('mail::button', ['url' => $magicLink])
Log In
@endcomponent
If you did not request this login link, please ignore this email.
Thanks,<br>
{{ config('app.name') }}
@endcomponent
For this to work you need to setup your email driver on the `.env` file
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=redacted
MAIL_PASSWORD=redacted
MAIL_ENCRYPTION=tls
[email protected]
Next, we need to create a controller that will handle the logic for sending the magic link and logging the user in. We can create a new controller called `MagicLinkController` using the following command:
php artisan make:controller MagicLinkController
In the `MagicLinkController`, we can define two methods: `sendMagicLink` and `loginWithMagicLink`.
The `sendMagicLink` method will generate a unique token and send an email to the user with a link containing this token. Here’s an example implementation:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use Carbon\Carbon;
class MagicLinkController extends Controller
{
public function sendMagicLink(Request $request)
{
$request->validate([
'email' => 'required|email',
]);
if (!RateLimiter::attempt('send-magic-link:' . $request->ip(), 5, 1)) {
return redirect()->back()->withErrors(['email' => 'Too many requests. Please try again later.']);
}
$user = User::where('email', $request->email)->first();
if (!$user) {
return redirect()->back()->withErrors(['email' => 'We could not find a user with that email address.']);
}
$user->login_token = null;
$user->login_token_created_at = null;
$user->save();
$token = bin2hex(random_bytes(16));
$user->login_token = $token;
$user->login_token_created_at = Carbon::now();
$user->save();
$magicLink = url('/login/magic/' . $token);
Mail::to($user)->send(new MagicLinkEmail($magicLink));
return redirect()->back()->with('success', 'We have sent you a magic link. Please check your email.');
}
}
And the route for the above controller:
Route::post('/login/magic', [MagicLinkController::class, 'sendMagicLink']);
The `loginWithMagicLink` method will check the token in the URL and log the user in if it’s valid. Here’s an example implementation:
<?php
public function loginWithMagicLink($token)
{
$user = User::where('login_token', $token)
->where('login_token_created_at', '>=', now()->subMinutes(5))
->first();
if (!$user) {
return redirect('/login')->withErrors(['magic_link' => 'The magic link is invalid or has expired.']);
}
Auth::login($user);
$user->login_token = null;
$user->login_token_created_at = null;
$user->save();
return redirect('/home');
}
Finally, we need to create a new route in `api.php` for the `loginWithMagicLink` method:
Route::get('/login/magic/{token}', [MagicLinkController::class ,'loginWithMagicLink']);
With these changes, we’ve created a simple passwordless authentication system using Laravel.
Before sending the magic link, we should validate the email address to ensure it’s a valid format. We can do this using Laravel’s built-in validation. In the sendMagicLink method of the MagicLinkController, add the following code:
$this->validate($request, [
'email' => 'required|email'
]);
This will ensure that the email address is not empty and is in a valid email format. If the validation fails, Laravel will automatically redirect the user back to the form with an error message.
Improving Security with Rate Limiting
To prevent attackers from generating large numbers of magic links, we should add rate limiting to the sendMagicLink method. We can use Laravel’s built-in rate limiting functionality to achieve this.
In the sendMagicLink method, add the following code:
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
$this->incrementLoginAttempts($request);
This code will prevent a user from sending more than a certain number of magic links in a specified time period. By default, Laravel allows a user to send 6 magic links per minute. If the user exceeds this limit, Laravel will lock them out for a certain period of time.
Improving Security with Two-Factor Authentication
To further improve the security of our passwordless authentication system, we can add two-factor authentication. Two-factor authentication requires the user to provide two forms of identification to log in, such as a password and a code sent to their phone.
Laravel has built-in support for two-factor authentication using packages like Google Authenticator or Authy. You can add two-factor authentication by following the instructions in Laravel’s documentation. You can find the project code on the GitHub.
Conclusion
Passwordless authentication is a convenient and secure way to log into applications. In this article, we learned how to create a passwordless authentication system using PHP/Laravel. By following best practices like email validation, rate limiting, and two-factor authentication, we can make our passwordless authentication system even more secure.
Note: This article is focused only on the backend part of the system you will need to handle registration and login part.