Search

Passwordless Login with Laravel 8

post-title

Laravel provides simple authentication system out-of-the box. It provides register, login, logout and Email verification features in single command. But have you wonder, you can even create authentication system without using password. By using this feature, user don't have to remember the password.

In this article, we will go through how you can create register and login system without password in Laravel. We will use only email address to register and login. In the register, we will send link in the given email for email verification, while we will send login link everytime when user input email.

We will go through step by step from the beginning. The tutorial covers below steps:

  • Step 1: Create fresh Laravel application
  • Step 2: Configure database and mail settings
  • Step 3: Update user migration file and run migration
  • Step 4: Create authentication routes
  • Step 5: Create controller class
  • Step 6: Create mailable class for login and register
  • Step 7: Create mail templates for login and register
  • Step 8: Create blade view files for login and register
  • Step 9: Testing register and login feature

So let's start the tutorial with fresh Laravel application.

Step 1: Create fresh Laravel application

In the beginning of the tutorial, we will create fresh Laravel application using Composer command. To create Laravel application, open the Terminal and run following command.

composer create-project laravel/laravel passwordless --prefer-dist

After the project is created, change Terminal directory to project root.

cd passwordless

Step 2: Configure database and mail settings

We will also need to configure database and mail settings into Laravel application. Laravel application stores all environment variables at .env file of root directory. Open the project into your favourite IDE.

Now open .env file and change database and email credentials accoring to your MySQL settings and mail credentials.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=passwordless
DB_USERNAME=root
DB_PASSWORD=secret

MAIL_MAILER=smtp
MAIL_HOST=us-v1.mailjet.com
MAIL_PORT=587
MAIL_USERNAME=26**********7ba26
MAIL_PASSWORD=YPAMv*******1yoO78
MAIL_ENCRYPTION=tls
MAIL_FROM_NAME="Laravelcode"
MAIL_FROM_ADDRESS=harsukh21@gmail.com

Step 3: Update user migration file and run migration

We are using users table for authentication, but we are not using password field, instead we will use token field. So open user migration file at database/migrations directory and update password fields to below.

<?php

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

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    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();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

After updating migration, run migrate command into Terminal to create users table into database.

php artisan migrate

Step 4: Create authentication routes

In this step, we will add required routes for register and login feature. Laravel routes are stored at routes directory. Open routes/web.php file and add following routes into it.

<?php

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

Route::get('register', [AuthController::class, 'registerForm'])->name('registerForm');
Route::post('register', [AuthController::class, 'register'])->name('register');
Route::get('verify', [AuthController::class, 'verify'])->name('verify');
Route::get('login', [AuthController::class, 'loginForm'])->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');

Step 5: Create controller class

We have created the required routes for authentication. Now we need to create controller class and class methods where we will input application logic. For the simplicity of tutorial, we have created only one controller for all routes. You can create seperate controller for register and login feature. Run the following command into Terminal to generate controller class.

php artisan make:controller AuthController

This will create AuthController class at App/Http/Controllers/AuthController.php file. Now open this file and add class methods as we have registered into routes.

<?php

namespace App\Http\Controllers;

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

class AuthController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except(['logout', 'dashboard']);
        $this->middleware('auth')->only(['logout', 'dashboard']);
    }

    /**
     * register form.
     *
     * @return void
     */
    public function registerForm()
    {
        return view('register');
    }

    /**
     * register form submit.
     *
     * @return void
     */
    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.');
    }

    /**
     * register verified.
     *
     * @return void
     */
    public function verify(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.');
        }

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

    /**
     * login form.
     *
     * @return void
     */
    public function loginForm()
    {
        return view('login');
    }

    /**
     * login link sent to mail.
     *
     * @return void
     */
    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', 'Email is not registered.');
    }

    /**
     * login process.
     *
     * @return void
     */
    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.');
    }

    /**
     * logout process.
     *
     * @return void
     */
    public function logout(Request $request)
    {
        auth()->guard('web')->logout();
        \Session::flush();

        return redirect()->route('loginForm')->with('success', 'you are successfully logged out.');
    }

    /**
     * dashboard page
     *
     * @return void
     */
    public function dashboard()
    {
        return view('dashboard');
    }
}

Step 6: Create mailable class for login and register

As per the above step, we are sending email verification and login mail to the registered email address. To send the email, we need to create mailable class and mail templates. In this step, we will create mailable classes RegisterMail and LoginMail. In the next step, we will create mail template file. Run the following Artisan commands one by one to generate mailable class.

php artisan make:mail RegisterMail
php artisan make:mail LoginMail

This will create Mailable classes at App/Mail directory. Open RegisterMail class and add below code into it.

<?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;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct($token)
    {
        $this->token = $token;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->view('registerMail')
            ->subject('Email verification mail.')
            ->with('token', $this->token);
    }
}

And put the below code into LoginMail.php file.

<?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;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct($token)
    {
        $this->token = $token;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->view('loginMail')
            ->subject('Click on the link to login.')
            ->with('token', $this->token);
    }
}

Step 7: Create mail templates for login and register

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

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

<!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 loginMail.blade.php file and input below HTML code into it.

<!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>

Step 8: Create blade view files for login, register and dashboard

In this final steps, we will create all front end blade files for view.

Create below blade views into resources/views directory.

register.blade.php file to register user.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Please register</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/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="name@example.com">
                        @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>

login.blade.php file to send login link.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Please login</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/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="name@example.com">
                        @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>

And dashboard.blade.php file after successfully authenticated.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Dashboard</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/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>

Step 9: Testing register and login feature

So, application coding part is completed. Now we can start testing application. First start Laravel server using following command into Terminal.

php artisan serve

In your browser, go to the url http://localhost:8000/register. Input name and email address and register.

Check for the email in your inbox and click Verify button in mail.

To test login feature, go to http://localhost:8000/login. Enter email address and login. You will get login link in the registered email address.

Conclusion

We have completed the article. So far, we have created register and login feature without password. This type of feature provides extra layer of security without OTP. We will also create another article on how to authenticate using OTP feature.

I hope you liked this article. Thank you for giving time in reading article. Happy coding.