How to set up Laravel Magic Link?
Photo by Anton Darius on Unsplash
User authentication is crucial for making web applications secure and easy to use. Traditionally, passwords have been the main way to log in, but they come with many problems. People often forget their passwords, reuse weak ones, or face the hassle of resetting them. These issues can also lead to security risks like stolen credentials.
Magic link login offers a simple and secure alternative. Instead of relying on passwords, users receive a unique, one-time-use link via email to log in. This approach removes the need for passwords entirely, making the process easier, faster, and safer for everyone.
In this article, I will walk you through setting up a magic link in your Laravel project.
Prerequisite
To follow this guide, you need:
- Laravel and Composer are installed in your local system.
- Basic knowledge of Laravel.
Step 1: Create a Laravel Project
-
Create a Laravel project using the Breeze starter kit:
laravel new magic-link-demo --breeze
-
Navigate to your project directory and open it in your favorite code editor. Open the terminal and serve your project using bthe elow command:
php artisan serve
-
Now your laravel app is serving at
http://127.0.0.1:8000/
Step 2: Register new user
Once you serve the application & open it you can see the navbar you will find a register button in top right corner. Clicking on this will open a register form, please register a new user using the registration form.
Step 3: Create a view for the magic link login page.
Like a login page, we will create a separate page for a magic link login. We will have only one field in this form(email address). Below is the code for the magic link login page view.
<x-guest-layout>
<!-- Session Status -->
<x-auth-session-status class="mb-4" :status="session('status')" />
<form method="POST" action="{{ route('magic-link-login.store') }}">
@csrf
<!-- Email Address -->
<div>
<x-input-label for="email" :value="__('Email')" />
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required
autofocus autocomplete="username" />
<x-input-error :messages="$errors->get('email')" class="mt-2" />
</div>
<div class="flex items-center justify-end mt-4">
<x-primary-button class="ms-3">
{{ __('Log in') }}
</x-primary-button>
</div>
</form>
</x-guest-layout>
Step 4: Create a controller for the magic link
Let’s create a controller for the magic link by running the following command:
php artisan make:controller MagicLinkController
Now we will define the following methods in this controller for handling the magic link login.
-
create()
This method renders a magic link login view.public function create() { return view('auth.magic-link-login'); }
This will create a
MagicLinkController
in theapp/Http/Controllers
directory. -
store()
This method handles the form submission, generates a Magic Link, and sends it via email.public function store() { request()->validate(['email' => 'required|email']); // Find the user by their email $user = User::where('email', request()->email)->first(); // If the email doesn’t match any user, return an error if (! $user) { return back()->withErrors(['email' => 'Account with matching email address not found.']); } // Generate a temporary signed URL for the user to login. $url = url()->temporarySignedRoute( 'login.token', now()->addMinutes(30), ['user' => $user->id] ); // Notify the user via email $user->notify(new MagicLinkLogin($url)); return back()->with('status', 'A login link has been sent to your email address.'); }
-
LoginViaToken()
This method authenticates users when they log in via Magic Link.public function loginViaToken() { $user = User::findOrFail(request()->user); Auth::login($user); Log::info('User logged in via token.', ['email' => $user->email]); request()->session()->regenerate(); return redirect()->intended(route('dashboard', absolute: false)); }
Step 5: Create the Notification
To send the Magic Link email, we’ll use Laravel’s Notification system.
-
Generate a MagicLinkLogin notification by following the below command:
php artisan make:notification MagicLinkLogin
This will create
MagicLinkLogin.php
file inapp/Notifications
. -
configure email by modifying
toMail
method in this file. Here is the complete code for this.<?php namespace App\Notifications; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; class MagicLinkLogin extends Notification { use Queueable; /** * Create a new notification instance. */ public function __construct(public string $url) { // } /** * Get the notification's delivery channels. * * @return array<int, string> */ public function via(object $notifiable): array { return ['mail']; } /** * Get the mail representation of the notification. */ public function toMail(object $notifiable): MailMessage { return (new MailMessage) ->line('Click here to login instantly.') ->action('Login', $this->url) ->line('Thank you for using our application!'); } /** * Get the array representation of the notification. * * @return array<string, mixed> */ public function toArray(object $notifiable): array { return [ // ]; } }
Step 6: Configure routes.
Add the following routes to routes/web.php
Route::get('/magic-link-login', [MagicLinkController::class, 'create'])->name('magic-link-login');
Route::post('/magic-link-login', [MagicLinkController::class, 'store'])->name('magic-link-login.store');
Route::get('/magic-link-login/{user}', [MagicLinkController::class, 'loginViaToken'])
->name('login.token')
->middleware('signed');
Here is the details about these routes:
-
GET /magic-link-login
: Displays the Magic Link login form. -
POST /magic-link-login
: Handles form submissions and sends the Magic Link. -
GET /magic-link-login/{user}
: Logs in the user when they click the Magic Link.
Step 7: Configure the email mailer.
For development, we’ll use the log
driver to log emails instead of sending them. Update your .env
file:
MAIL_MAILER=log
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
You can also configure other mailers like smtp, mailgun, and Postmark.
Step 8: Add a magic link option to the login page.
To make the Magic Link login accessible, we’ll add a button to our existing login page. This allows users to choose between traditional login (email + password) or Magic Link login.
Here’s the complete code for your updated login page:
<x-guest-layout>
<!-- Session Status -->
<x-auth-session-status class="mb-4" :status="session('status')" />
<form method="POST" action="{{ route('login') }}">
@csrf
<!-- Email Address -->
<div>
<x-input-label for="email" :value="__('Email')" />
<x-text-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required
autofocus autocomplete="username" />
<x-input-error :messages="$errors->get('email')" class="mt-2" />
</div>
<!-- Password -->
<div class="mt-4">
<x-input-label for="password" :value="__('Password')" />
<x-text-input id="password" class="block mt-1 w-full" type="password" name="password" required
autocomplete="current-password" />
<x-input-error :messages="$errors->get('password')" class="mt-2" />
</div>
<!-- Remember Me -->
<div class="block mt-4">
<label for="remember_me" class="inline-flex items-center">
<input id="remember_me" type="checkbox"
class="rounded border-gray-300 text-indigo-600 shadow-sm focus:ring-indigo-500" name="remember">
<span class="ms-2 text-sm text-gray-600">{{ __('Remember me') }}</span>
</label>
</div>
<div class="flex items-center justify-between mt-4">
@if (Route::has('password.request'))
<a class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
href="{{ route('password.request') }}">
{{ __('Forgot your password?') }}
</a>
@endif
<x-primary-button class="ms-3">
{{ __('Log in') }}
</x-primary-button>
</div>
<p class="py-4 text-center font-semibold text-xl">OR</p>
<div class="flex justify-center">
<a class="inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150"
href="{{ route('magic-link-login') }}">
{{ __('Login with magic link') }}
</a>
</div>
</form>
</x-guest-layout>
Step 9: Test the magic link feature.
-
Navigate to the main login page. You’ll notice a new “Login with Magic Link” button under the standard login form
-
When you click the button, you’ll be redirected to the Magic Link login page. This page contains a form where users can enter their email address. Enter the details of the registered user and submit the form
-
If you’ve set the
MAIL_MAILER=log
in your.env
file, open the Laravel logs to find the Magic Link URL or if you have configured a mailer please check your mailbox. -
Click the link to log in seamlessly! You will be redirected to the application dashboard directly.
Congratulations!🥳 You have successfully set up & tested the magic link login in your laravel application.
Conclusion
You’ve successfully implemented Magic Link Login in your Laravel application! This feature is a game-changer for user experience, offering you a secure and hassle-free way to log in.🎉
Btw, if you want to save time and kickstart your project easily, you can consider choosing a pre-built SaaS Boilerplate like Jetship Laravel Starter Kit.
It simplifies authentication with its One-Click Magic Link Setup and offers various authentication methods, including traditional email/password and social logins via Google, GitHub, and Twitter.lara
driesvints liked this article
Other articles you might like
Access Route Model-Bound Models in Laravel Form Requests with "#[RouteParameter]"
Introduction I've recently been using the new #[RouteParameter] attribute in Laravel, and I've been...
Laravel Reverb and Vue 3 + TypeScript : Add Realtime to your App
In many modern web applications, WebSockets are used to implement realtime, live-updating user inter...
Access Laravel before and after running Pest tests
How to access the Laravel ecosystem by simulating the beforeAll and afterAll methods in a Pest test....
The Laravel portal for problem solving, knowledge sharing and community building.
The community