📜 ⬆️ ⬇️

Create passwordless authentication in Laravel using only email

Recently, I was working on a project where one of the pain points was the user's password. The administrator added users to the application, so they do not have a password, and it was extremely inconvenient to force them to invent a password when they log in for the first time after registration.

So, we decided to try the method of passwordless entry. If you have never had the opportunity to work with this, we will tell you how it works:

On the login page, the user enters his email address, which will receive a link to the login. Clicking on the link confirms the identity of the user without having to enter a password, since the link for each user login is unique.
')
Let's start creating!

image


New application and make: auth


First, we will create our application by connecting authentication:

laravel new medium-login cd medium-login php artisan make:auth 


Now we have all the necessary files for authorization, including the view. Let's start with them.

Change login and registration page


Of course, combining a login with a password is a pretty good idea, but we need to abandon the password field on both forms.

Open the `resources / views / auth / login.blade.php` file and remove the group responsible for entering the password (label, input and wrapper). Save, close.

Now open the `resources / views / auth / register.blade.php` file and delete the groups responsible for entering the password (` password`) and confirming the password (`password-reset`). Save, close.

You can later add instructions on the login method on the authentication page, as well as post links to reset the password, but that later.

Change registration routes


So, we need to change the route, indicating the entry and registration points. Take a look at the `AuthController` controller.

First, we notice the `validator` method, which returns the validation of the password field. Since he is responsible for the process of registering an account, we need to get rid of his binding to the password.

Ultimately, the function should look like this:

 // app/http/Controllers/Auth/AuthController.php protected function validator(array $data) { return Validator::make($data, [ 'name' => 'required|max:255', 'email' => 'required|email|max:255|unique:users', ]); } 


We will do the same for the `Create` method, bringing it to the form:

 // app/http/Controllers/Auth/AuthController.php protected function create(array $data) { return User::create([ 'name' => $data['name'], 'email' => $data['email'], ]); } 


Overlapping of the `login` route


As you can see, there are no methods for registering users. They are hidden in the `AuthenticatesAndRegistersUsers` thread, which uses the` AuthenticatesUsers` authentication credentials and `RegistersUsers` authentication credentials. You can go to the `AuthenticatesUsers` treyt and at the end of the file find the user authentication method named` login`.

Everything that happens there is based on secure passwords, although this method can be replaced ...

The goal of our new method is to send a login link to the user's email. Let's go back to the `AuthController` controller and add a login method that overrides` login` in `AuthenticatesUsers`:

 // app/http/Controllers/Auth/AuthController.php public function login(Request $request) { // validate that this is a real email address // send off a login email // show the users a view saying "check your email" } 


Confirming the reality of email addresses


Confirming the reality of the email address for a registered user is very simple:

 $this->validate($request, ['email' => 'required|email|exists:users']); 


Send email


Next, we need to send the user a link to the entrance. It takes a little longer.

Creating a structure for the formation and verification of email tokens


If you are familiar with the form of the database structure `password_reset`, then it will be easier for you, because we will create something similar. Every time someone tries to log in, we need to add an entry to the table, which will record the email address and unique token sent in the email as a URL, as well as the creation date and lifetime of the record.

Ultimately, we will use the URL to create (and verify), for example: `myapp.com / auth / email-authenticate / 09ajfpoib23li4ub123p984h1234`. Since the lifetime of the token is limited, we must associate this URL with a specific user, tracking the email, the token and the creation date for each entry in the table.

So let's create a migration for it:

 php artisan make:migration create_email_logins_table --create=email_logins 


And add some fields to it:

 Schema::create('email_logins', function (Blueprint $table) { $table->string('email')->index(); $table->string('token')->index(); $table->timestamps(); }); 


Note: if you wish, you can use the value of the id column instead of a token, but there are several reasons for better options. In any case, you decide.


Now, let's create a model.

 php artisan make:model EmailLogin 


Edit the file (`app / EmailLogin.php`) and make it simple for us by creating an instance with the necessary properties:

 class EmailLogin extends Model { public $fillable = ['email', 'token']; } 


And when we want to find a user, we have to use email, not an identifier, manually linking the email column:

 class EmailLogin extends Model { public $fillable = ['email', 'token']; public function user() { return $this->hasOne(\App\User::class, 'email', 'email'); } } 


Creating a token


Now we are ready to create an email message. We will use a URL containing a unique token generated in advance.

We need to understand how we will create and store the token. To do this, we need to create an instance of `EmailLogin`, so let's proceed:

 public function login() { $this->validate($request, ['email' => 'required|email|exists:users']); $emailLogin = EmailLogin::createForEmail($request->input('email')); } 


Let's add this method to `EmailLogin`:

 class EmailLogin extends Model { ... public static function createForEmail($email) { return self::create([ 'email' => $email, 'token' => str_random(20) ]); } } 


We generate a random token and create an instance of the `EmailToken` class, getting it back.

Forming a URL for emailing


So, we need to use `EmailToken` to form a URL before sending a message to the user.

 public function login() { $this->validate($request, ['email' => 'required|email|exists:users']); $emailLogin = EmailLogin::createForEmail($request->input('email')); $url = route('auth.email-authenticate', [ 'token' => $emailLogin->token ]); } 


Let's create a route for it:

 // app/Http/routes.php Route::get('auth/email-authenticate/{token}', [ 'as' => 'auth.email-authenticate', 'uses' => 'Auth\AuthController@authenticateEmail' ]); 


... and add a method to the controller for this route to work:

 class AuthController { ... public function authenticateEmail($token) { $emailLogin = EmailLogin::validFromToken($token); Auth::login($emailLogin->user); return redirect('home'); } } 


... and add the `validFromToken` method to check the token:

 class EmailLogin { ... public static function validFromToken($token) { return self::where('token', $token) ->where('created_at', '>', Carbon::parse('-15 minutes')) ->firstOrFail(); } 


Now we have an incoming route that takes into account the relevance of each token. If the token is current, the user will be redirected to `mysite.ru / home`.

Well, let's send a letter.

Sending letter


Add a call call call to our controller:

 public function login() { ... Mail::send('auth.emails.email-login', ['url' => $url], function ($m) use ($request) { $m->from('noreply@myapp.com', 'MyApp'); $m->to($request->input('email'))->subject('MyApp Login'); }); 


... and create a template:

 <!-- resources/views/auth/emails/email-login.blade.php --> Log in to MyApp here: <a href="{{ $url }}">{{ $url }}</a> 


Return pattern


You can arrange the template in any convenient way, but we just use the text: “Hey, we sent the soap to check it. It's all."

 return 'Login email sent. Go check your email.'; 


Joint entry


Take a look at our system. We have a new `login` method in the` AuthController` controller:

 public function login(Request $request) { $this->validate($request, ['email' => 'required|exists:users']); $emailLogin = EmailLogin::createForEmail($request->input('email')); $url = route('auth.email-authenticate', [ 'token' => $emailLogin->token ]); Mail::send('auth.emails.email-login', ['url' => $url], function ($m) use ($request) { $m->from('noreply@myapp.com', 'MyApp'); $m->to($request->input('email'))->subject('MyApp login'); }); return 'Login email sent. Go check your email.'; } 


We created several views by updating existing ones (we removed password entries in them). Also created a new route in `/ auth / email-authenticate`. And also created the EmailLogin `migration with a class of all its needs.

It's all!


And ... profit! Put all the examples in your code and get a fully functional password-free entry system.

To register a user, you only need to find out their email address. And when authorizing, in addition to their email-addresses, you will no longer need to remember or enter anything. No more forgotten passwords. Boom!

From translator


When translating the article, information was adapted for better readability by Russian-speaking users.

Source: https://habr.com/ru/post/279173/


All Articles