In this article, we are going to learn how to create a passwordless login system in Laravel. In here, we will use only the user's email address to register and login. In the registration, we will send a link to given email for email verification, while we will send login link everytime when user input email.

Setting up the Project

First you need to create a fresh laravel project by running below command in your terminal
1
composer create-project laravel/laravel laravel-passwordless-login

Then navigate to your project directory by using below command in your terminal

1
cd laravel-passwordless-login

Setting up Database

To setup up Database for our project, open the application using your favourite text editor and and then navigate to .env file in it and then change below section acording to your Database settings:

1
2
3
4
5
6
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel-passwordless
DB_USERNAME=root
DB_PASSWORD=

Configure mail settings

In here, for testing our emails we use one of the most popular mail testing platform Mailtrap. To configure mail settings, navigate to .env file in it and then change below section acording to your Mailtrap settings:

1
2
3
4
5
6
7
8
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=**********
MAIL_PASSWORD=**********
MAIL_ENCRYPTION=tls
MAIL_FROM_NAME="Larave Passwordless Login"
MAIL_FROM_ADDRESS=[email protected]

Create Migration

By default Laravel comes with a migration file named create_users_table which is used to create a table named users in your database. Then navigate to your users migration file and add below code on it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateUsersTable extends Migration
{
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->boolean('email_verified')->default(0)->comment('1 for verified and 0 for not verified');
$table->string('token');
$table->rememberToken();
$table->timestamps();
});
}

public function down()
{
Schema::dropIfExists('products');
}
}

Then run below command in your terminal to migrate migration file:

1
php artisan migrate

Create Product Controller

Now we need to create contoller to handle our core logic for handling our login/ register links and views. To create new controller:

1
php artisan make:controller AuthController

After that open your app/Http/Controllers/AuthController.php and paste below code in it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
<?php

namespace App\Http\Controllers;

use Auth;
use Mail;
use App\Mail\LoginMail;
use App\Mail\RegisterMail;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use App\Models\User;

class AuthController extends Controller
{

public function __construct()
{
$this->middleware('guest')->except(['logout', 'dashboard']);
$this->middleware('auth')->only(['logout', 'dashboard']);
}

/* function show register form */
public function showRegisterForm()
{
return view('auth.register');
}

/* function to register user */
public function register(Request $request)
{
$input = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|email|string|max:255|unique:users',
]);

$token = Str::random(30);

$user = new User;
$user->name = $input['name'];
$user->email = $input['email'];
$user->email_verified = '0';
$user->token = $token;
$user->save();

Mail::to($input['email'])->send(new RegisterMail($token));

return redirect()->back()->with(
'success',
'Verification mail sent, please check your inbox.'
);
}

/* function to verify token after user registration */
public function verifyToken(Request $request)
{

$input = $request->validate([
'token' => 'required|string',
]);

$user = User::where('token', $input['token'])
->where('email_verified', '0')
->first();

if ($user != null) {
User::where('token', $input['token'])
->update([
'email_verified' => '1',
'token' => ''
]);

Auth::login($user);

return redirect()->route('dashboard')->with(
'success',
'You are successfully registered.'
);
}
}

/* function to show login form */
public function showLoginForm()
{
return view('auth.login');
}

/* function to send login mail */
public function sendLink(Request $request)
{
$input = $request->validate([
'email' => 'required|email',
]);

$user = User::where('email', $input['email'])
->where('email_verified', '1')
->first();

if ($user != null) {
$token = Str::random(30);

User::where('email', $input['email'])
->where('email_verified', '1')
->update(['token' => $token]);

Mail::to($input['email'])->send(new LoginMail($token));

return redirect()->back()->with(
'success',
'Login link sent, please check your inbox.'
);
}

return redirect()->back()->with(
'error',
'Given Email is not exists with our system.'
);
}

/* function to login user */
public function login(Request $request)
{
$input = $request->validate([
'token' => 'required|string',
]);

$user = User::where('token', $input['token'])
->where('email_verified', '1')
->first();

if ($user != null) {
User::where('token', $input['token'])
->where('email_verified', '1')
->update(['token' => '']);

Auth::login($user);

return redirect()->route('dashboard')->with(
'success',
'You are successfully logged in.'
);
}

return redirect()->back()->with(
'error',
'Login link is not valid.'
);
}

/* function to load dashboard */
public function dashboard()
{
return view('dashboard');
}

/* function to logout user */
public function logout(Request $request)
{
auth()->guard('web')->logout();
\Session::flush();
return redirect()->route('loginForm')->with(
'success',
'You are successfully logged out.'
);
}

}

Create mailable classes

To send the email, we need to create mailable class. In this step, we will create mailable classes RegisterMail and LoginMail. Run the following Artisan commands to generate mailable class.
1
php artisan make:mail RegisterMail

It will create a file called RegisterMail.php in app/Mail/RegisterMail.php. Then paste below code in it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class RegisterMail extends Mailable
{
use Queueable, SerializesModels;

public $token;

public function __construct($token)
{
$this->token = $token;
}

public function build()
{
return $this->view('mail.register')
->subject('Email verification mail.')
->with('token', $this->token);
}
}

Now we nned to create mailable class for login. To do it, run the following command:

1
php artisan make:mail LoginMail

It will create a file called LoginMail.php in app/Mail/LoginMail.php. Then paste below code in it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class LoginMail extends Mailable
{
use Queueable, SerializesModels;

public $token;

public function __construct($token)
{
$this->token = $token;
}

public function build()
{
return $this->view('mail.login')
->subject('Click on the link to login.')
->with('token', $this->token);
}
}

Create mail templates

We have configured mailable class. Now we need to create mail template blade file with link. We will send this template with email. Laravel blade files are located at resources/views directory.

For verification mail, create register.blade.php file at resources/views/mail directory and input below HTML code into it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Verify the email address by clicking the link</title>
<style type="text/css">
.container {
text-align: center;
margin: 50px;
}

.btn {
background-color: #ccc;
padding: 10px 20px;
display: inline-block;
}

</style>
</head>
<body>
<div class="container">
<h2>{{ config('app.name') }}</h2>
<div class="text-center">
<a class="btn" href="{{ route('verify', ['token' => $token]) }}">Click to Verify</a>
</div>
</div>
</body>
</html>

And for login mail, create login.blade.php file at resources/views/mail directory and input below HTML code into it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Login by clicking the link</title>
<style type="text/css">
.container {
text-align: center;
margin: 50px;
}
.btn {
background-color: #ccc;
padding: 10px 20px;
display: inline-block;
}
</style>
</head>
<body>
<div class="container">
<h2>{{ config('app.name') }}</h2>
<div class="text-center">
<a class="btn" href="{{ route('loginVerify', ['token' => $token]) }}">Click to Login</a>
</div>
</div>
</body>
</html>

Create Views

Inside resources -> views create folder called auth folder and cretae file called register.blade.php and paste below code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Please register</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row">
<div class="m-3">
<h2>{{ config('app.name') }}</h2>
@if(\Session::has('error'))
<div class="alert alert-danger my-1 w-50">{{ \Session::get('error') }}</div>
{{ \Session::forget('error') }}
@endif
@if(\Session::has('success'))
<div class="alert alert-success my-1 w-50">{{ \Session::get('success') }}</div>
{{ \Session::forget('success') }}
@endif
<p class="lead">Please register</p>
<form action="{{ route('register') }}" method="post" class="w-50">
@csrf
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" name="name" placeholder="Name">
@error('name')
<div class="alert alert-danger my-1">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label for="email" class="form-label">Email address</label>
<input type="email" class="form-control" id="email" name="email" placeholder="[email protected]">
@error('email')
<div class="alert alert-danger my-1">{{ $message }}</div>
@enderror
</div>
<input type="submit" class="btn btn-primary">
</form>
</div>
</div>
</div>
</body>
</html>

Cretae file called login.blade.php and paste below code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Please login</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row">
<div class="m-3">
<h2>{{ config('app.name') }}</h2>
@if(\Session::has('error'))
<div class="alert alert-danger my-1 w-50">{{ \Session::get('error') }}</div>
{{ \Session::forget('error') }}
@endif
@if(\Session::has('success'))
<div class="alert alert-success my-1 w-50">{{ \Session::get('success') }}</div>
{{ \Session::forget('success') }}
@endif
<p class="lead">Please login</p>
<form action="{{ route('sendLink') }}" method="post" class="w-50">
@csrf
<div class="mb-3">
<label for="email" class="form-label">Email address</label>
<input type="email" class="form-control" id="email" name="email" placeholder="[email protected]">
@error('email')
<div class="alert alert-danger my-1">{{ $message }}</div>
@enderror
</div>
<input type="submit" class="btn btn-primary">
</form>
</div>
</div>
</div>
</body>
</html>

Inside resources -> views cretae file called dashboard.blade.php and paste below code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Dashboard</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container m-3">
<h2>{{ config('app.name') }}</h2>
<a href="{{ route('logout') }}">Logout</a>
<div class="row">
<div class="text-center">
<p class="lead">Welcome! {{ Auth::user()->name }}</p>
</div>
</div>
</div>
</body>
</html>

Define Routes

Navigate to routes/web.php file and paste below code:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\AuthController;

Route::get('/', [AuthController::class, 'showRegisterForm'])->name('show.register');
Route::post('register', [AuthController::class, 'register'])->name('register');
Route::get('verify', [AuthController::class, 'verifyToken'])->name('verify');
Route::get('login', [AuthController::class, 'showLoginForm'])->name('loginForm');
Route::post('send-link', [AuthController::class, 'sendLink'])->name('sendLink');
Route::get('login-verify', [AuthController::class, 'login'])->name('loginVerify');
Route::get('dashboard', [AuthController::class, 'dashboard'])->name('dashboard');
Route::get('logout', [AuthController::class, 'logout'])->name('logout');

Run our application

Now we have already finished our application. Now we can run your application using below command:
1
php artisan serve

Now, navigate to http://localhost:8000 in your browser.

Conclusion

In this tutorial, we passwordless login in Laravel. If you have any issue regarding this tutorial, mention your issue in comment section or reach me through my E-mail. You can obtain complete source code and asset files for this tutorial from this GitHub repository.

Happy Coding