Может войдёшь?
Черновики Написать статью Профиль

Создание беспарольной системы аутентификации по email в стиле Medium

перевод

  1. 1. Новое приложение и make:auth
  2. 2. Изменение страниц входа и регистрации
  3. 3. Изменение маршрутов регистрации
  4. 4. Переопределение маршрута login
  5. 5. Проверка email
  6. 6. Отправка письма для входа в систему
    1. 6.1. Создание структуры для генерации и проверки почтовых токенов входа
    2. 6.2. Создание токена
    3. 6.3. Создание URL для отправки на email
    4. 6.4. Отправка email
    5. 6.5. Возврат временного представления
  7. 7. Соединение всего воедино
  8. 8. Вот и всё!
/packages/proger/habravel/uploads/353-log-in-now-838x275.png

Недавно я работал над проектом, где одной из главных трудностей были пользовательские пароли. Пользователей завели в систему администраторы, поэтому для них не были заданы пароли. А если бы пришлось вынуждать их вводить и запоминать пароли, то это серьёзно снизило бы юзабилити проекта.

Поэтому мы решили попробовать беспарольный вход в стиле Medium/Slack. Если вы с таким не сталкивались, то в двух словах это работает так: вводите свой email на странице входа в систему, получаете письмо со ссылкой на вход, щёлкаете по ссылке и входите в систему. Доступ к вашему адресу электронной почты удостоверяет вашу личность не требуя пароля.

Давайте вместе сделаем такой вход.

Новое приложение и make:auth

Первым делом создадим наше laravel-приложение и сгенерируем систему аутентификации:

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

Теперь у нас есть ряд новых файлов, связанных с аутентификацией, включая страницы входа и регистрации. Давайте начнём с настройки этих файлов.

Изменение страниц входа и регистрации

/packages/proger/habravel/uploads/353-no-password-358x352.png

Страницы входа в систему и регистрации довольно хороши, но нам надо убрать из них поле пароля.

Откройте страницу входа resources/views/auth/login.blade.php и удалите всю группу формы password (метки, поля ввода и обёртку xml<div>). Сохраните и закройте.

Откройте страницу регистрации resources/views/auth/register.blade.php и также удалите группы формы password и password-reset. Сохраните и закройте.

Возможно, позже вы захотите добавить некоторые инструкции на обеих страницах, описывающие, как будет работать наша аутентификация, а также добавить ссылки для сброса пароля. Но пока этого будет вполне достаточно.

Изменение маршрутов регистрации

Теперь нам нужно обновить маршрут, на который указывают формы входа и регистрации. Давайте перейдём к AuthController и посмотрим, что у нас есть.

Во-первых, мы заметим метод validator, который возвращает валидатор для поля password. Это валидатор для регистрации учётной записи, так что давайте избавимся от этого пароля.

В конечном итоге функция должна выглядеть так:

PHP
// 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',
    ]);
}

И сделаем то же самое для метода create, который также используется для регистрации:

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

Переопределение маршрута login

Как вы могли заметить, здесь нет методов для входа пользователей. Они скрыты в типаже AuthenticatesAndRegistersUsers, который просто выдает вам типажи AuthenticatesUsers и RegistersUsers. Вы можете перейти к типажу AuthenticatesUsers и найти в нём метод login, который и позволяет входить пользователям в систему.

Всё, что там происходит, основано на входе с использованием пароля. Поэтому давайте переопределим этот метод до самого основания.

Целью нашего нового метода будет отправка пользователю письма и вывод подсказки о необходимости проверить почту. Давайте перейдем к AuthController и добавим метод login, чтобы переопределить тот, который есть в AuthenticatesUsers.

PHP
// app/http/Controllers/Auth/AuthController.php
public function login(Request $request)
{
    
// проверяем, что это реальный email,
    // отправляем письмо для входа,
    // показываем пользователю представление с текстом "проверьте почту".
}

Проверка email

Сначала давайте проверим адрес email пользователя. Это довольно просто:

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

Отправка письма для входа в систему

Затем нам надо послать письмо на указанный адрес и пригласить пользователя войти. Тут потребуется немного больше усилий.

Создание структуры для генерации и проверки почтовых токенов входа

Если вы знакомы со структурой базы данных password_reset, вам повезло. Мы будем создавать что-то очень похожее. Каждый раз, когда кто-то пытается войти в систему, мы должны будем добавить в таблицу запись, включающую адрес электронной почты, только что созданный уникальный токен (его мы отправим в электронном письме как часть URL), и дату создания записи для отслеживания срока действия ссылки.

Впоследствии мы будем использовать эту запись, чтобы генерировать (и проверять) вот такие URL: myapp.com/auth/email-authenticate/09ajfpoib23li4ub123p984h1234. Вход в систему по данной ссылке истечёт через заданный промежуток времени. Нам нужно будет связать данный URL с определённым пользователем, таким образом, мы должны зафиксировать email, token и created_at для каждой записи в этой таблице.

Давайте создадим миграцию для неё:

shphp artisan make:migration create_email_logins_table --create=email_logins

И добавим в неё несколько полей:

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

Если хотите, вы можете использовать столбец id внешнего ключа вместо email. Есть несколько причин, по которым использовать его лучше, чем просто email. Но мне нравится просто email. Решать вам.

Теперь давайте создадим модель.

shphp artisan make:model EmailLogin

Отредактируйте файл (app/EmailLogin.php) так, чтобы нам можно было просто создать экземпляр с нужными свойствами:

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

И затем мы захотим связать каждый экземпляр с пользователем. А поскольку в нашем примере мы отслеживаем пользователя по email, а не по id, мы должны вручную привязать столбец таблицы email:

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

    public function 
user()
    {
        return 
$this->hasOne(\App\User::class, 'email''email');
    }
}

Создание токена

Теперь можно создать письмо. Мы собираемся отправить конечному пользователю письмо с URL, в котором будет сгенерированный ранее уникальный токен.

Сначала давайте определимся, каким образом мы будем создавать и хранить эти токены. Нам нужно создать экземпляр EmailLogin, с этого и начнём:

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

  
$emailLogin EmailLogin::createForEmail($request->input('email'));
}

Давайте добавим этот метод в EmailLogin:

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

Теперь мы создаём экземпляр класса EmailLogin, в котором генерируем рандомный токен, после чего возвращаем этот экземпляр обратно.

Создание URL для отправки на email

Теперь нам надо использовать этот EmailToken для создания URL, который мы можем отправить нашему пользователю в письме.

PHP
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
  
]);
}

Давайте создадим маршрут для этого:

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

... и создадим метод контроллера для выполнения этого маршрута:

PHP
class AuthController
{
  ...
  public function 
authenticateEmail($token)
  {
    
$emailLogin EmailLogin::validFromToken($token);

    
Auth::login($emailLogin->user);

    return 
redirect('home');
  }
}

... и давайте создадим метод validFromToken:

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

Теперь у нас есть входящий маршрут с проверенным непросроченным токеном, который вводит пользователя и перенаправляет его к home. Давайте отправим это письмо.

Отправка email

Давайте добавим вызов «отправки почты» в метод нашего контроллера:

PHP
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');
  });

... и создадим шаблон письма:

xml<!— resources/views/auth/emails/email-login.blade.php —>
Войти в систему по этой ссылке: <a href="{{ $url }}">{{ $url }}</a>

Возврат временного представления

Вы можете создать представление каким угодно образом, но в целом нам просто нужно сообщить пользователю «Эй, мы послали тебе письмо, загляни в свой почтовый ящик». И всё.

PHP
return 'Письмо для входа в систему отправлено. Проверьте свою электронную почту.';

Соединение всего воедино

Давайте взглянем на нашу систему. У нас есть новый метод login в нашем AuthController:

PHP
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 
'Письмо для входа в систему отправлено. Проверьте свою электронную почту.';
}

Мы создали несколько новых представлений. Мы обновили старые представления — убрали поля паролей. Мы создали новый маршрут в /auth/email-authenticate. И мы создали миграцию EmailLogin и класс для всего этого.

Вот и всё!

Готово! Соедините все эти части, и у вас получится полностью функциональная, беспарольная система входа.

Когда ваши пользователи регистрируются в системе, они должны ввести только свой адрес электронной почты. Когда ваши пользователи заходят в систему, они должны ввести только свой адрес электронной почты. Теперь не будет никаких забытых паролей. Вуаля!

Как вы считаете, полезен ли этот материал? Да Нет

Комментарии (11)

VitalN

https://habrahabr.ru/post/279173/

Helldar

Дак это ж от меня перевод статьи) На Хабре выложил 14 марта.
Вот пруф: https://habrahabr.ru/post/279173/

Proger_XP
  1. Дак это ж от меня перевод статьи)

Статью здесь переводили, очевидно, с нуля.

Helldar

Очевидно, провели рефакторинг статьи.
Жаль, вчера не заскринил ибо предисловие было 1 в 1 со статьи на Хабре...

Proger_XP

Вот предисловие первой версии статьи здесь:

Недавно я работал над проектом, где одной из главных болевых точек были пароли пользователей. Пользователи были добавлены в систему вручную администратором. Так получилось, что у них не было паролей, когда они были добавлены, и вынужденное задание паролей пользователем и последующее их запоминание в системе было большой помехой при использовании сервиса.

Поэтому мы решили попробовать беспарольный Medium/Slack-inspired логин. Если у вас никогда не было возможности поработать с ним, то в двух словах это работает так: на странице входа в систему вводите свою адрес электронной почты, получаете письмо со ссылкой на вход, щёлкаете по ссылке, и теперь вы зарегистрированы. Доступ к вашему адресу электронной почты удостоверяет вашу личность без потребности в пароле.

Вот предисловие с Хабра:

Недавно я работал над проектом, где одной из болевых точек был пароль пользователя. Администратор добавлял пользователей в приложение, поэтому они не имеют пароля, а заставлять их придумывать пароль при первом после регистрации входе было крайне неудобно.

Итак, мы решили попробовать метод беспарольного входа. Если Вы никогда не имели возможности работать с этим, мы расскажем как это работает:

Helldar

Читаем внимательней предыдущий коммент:

  1. Очевидно, провели рефакторинг статьи.
  2. Жаль, вчера не заскринил ибо предисловие было 1 в 1 со статьи на Хабре...

Вчера != сегодня.

Proger_XP

Вот именно, читаем внимательнее. Я привёл первую версию, которая была опубликована на сайте. Совпадения заканчиваются после первых 8 слов.

Helldar

Где пруф, что это первая версия статьи?

Proger_XP

В админке сохраняется история правок. Скрин.

Helldar

Убедительно. Был не прав.
В любом случае, статья уже давно переведена.

Proger_XP
  1. В любом случае, статья уже давно переведена.

Да, мне стоило сказать об этом переводчику. Учтём на будущее.

Написать комментарий

Разметка: ? ?

Авторизуйся, чтобы прокомментировать.