[Перевод] Создание беспарольной аутентификации в Laravel, используя только email
Недавно я работал над проектом, где одной из болевых точек был пароль пользователя. Администратор добавлял пользователей в приложение, поэтому они не имеют пароля, а заставлять их придумывать пароль при первом после регистрации входе было крайне неудобно.
Итак, мы решили попробовать метод беспарольного входа. Если Вы никогда не имели возможности работать с этим, мы расскажем как это работает:
На странице входа в систему пользователь вводит свой email-адрес, на который получит ссылку на вход. Переход по ссылке подтверждает личность пользователя без необходимости ввода пароля, так как ссылка для каждого входа пользователя уникальная.
Начнем творить!
Новое приложение и make: auth
Вначале мы создадим наше приложение, подключив аутентификацию:
laravel new medium-login
cd medium-login
php artisan make:auth
Теперь у нас есть все необходимые для авторизации файлы, в том числе вьюхи. Давайте начнем с них.
Изменение страницы входа и регистрации
Конечно, сочетание логина с паролем довольно хорошая идея, но нам нужно отказаться от поля ввода пароля на обеих формах.
Откройте файл `resources/views/auth/login.blade.php` и удалите группу, отвечающую за ввод пароля (label, input и обертка). Сохраняем, закрываем.
Теперь открываем файл `resources/views/auth/register.blade.php` и удаляем группы, отвечающие за ввод пароля (`password`) и подтверждения пароля (`password-reset`). Сохраняем, закрываем.
Позже Вы можете добавить инструкцию о методе входа на странице аутентификации, а также разместите ссылки на сброс пароля, но это позже.
Изменение регистрационных роутов
Итак, нам нужно изменить роут, указывающий на точки входа и регистрации. Взглянем на контроллер ` AuthController`.
Во-первых, мы заметим метод `validator`, возвращающий валидацию поля пароля. Так как он отвечает за процесс регистрации учетной записи, нам нужно избавиться от его привязки к паролю.
В конечном итоге, функция должна выглядеть так:
// 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`, приведя его к виду:
// 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`.
Все, что там происходит, основывается на защищенных паролях, хотя этот метод можно и заменить…
Целью нашего нового метода является отправка на email пользователя ссылки для входа в систему. Давайте вернемся к контроллеру `AuthController` и добавим метод входа в систему, перекрывающий `login` в `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"
}
Подтверждение реальности email-адреса
Подтвердить реальность email адреса для зарегистрированного пользователя очень просто:
$this->validate($request, ['email' => 'required|email|exists:users']);
Отправка email-сообщения
Далее, нам необходимо отправить пользователю ссылку на вход. Это займет немного больше времени.
Создание структуры для формирования и проверки токенов email
Если Вы знакомы с формой структуры базы данных `password_reset`, то Вам будет проще, т.к. мы будем создавать нечто похожее. Каждый раз, когда кто-то пытается войти в систему, нам нужно добавлять запись в таблицу, которая будет фиксировать адрес электронной почты и уникальный токен, отправляемые в электронном письме в качестве URL, а также дату создания и срок жизни записи.
В конечном итоге мы будем использовать URL-адрес для создания (и проверки), например: `myapp.com/auth/email-authenticate/09ajfpoib23li4ub123p984h1234`. Так как срок жизни токена ограничен, мы должны связать этот URL с конкретным пользователем, отслеживая email, токен и дату создания для каждой записи таблицы.
Итак, создадим для него миграцию:
php artisan make:migration create_email_logins_table --create=email_logins
И добавим в нее несколько полей:
Schema::create('email_logins', function (Blueprint $table) {
$table->string('email')->index();
$table->string('token')->index();
$table->timestamps();
});
Примечание: при желании можно использовать значение колонки `id` вместо токена, но есть несколько причин более лучших вариантов. Во всяком случае, решать Вам.
Теперь, давайте создадим модель.
php artisan make:model EmailLogin
Отредактировать файл (`app/EmailLogin.php`) и сделать его простым для нас, создав экземпляр с нужными свойствами:
class EmailLogin extends Model
{
public $fillable = ['email', 'token'];
}
И когда хотим найти пользователя, мы должны использовать электронную почту, а не идентификатор, вручную связав столбец email:
class EmailLogin extends Model
{
public $fillable = ['email', 'token'];
public function user()
{
return $this->hasOne(\App\User::class, 'email', 'email');
}
}
Создание токена
Теперь мы готовы к созданию email-сообщения. Мы будем использовать URL-адрес, содержащий уникальный токен, сгенерированный заранее.
Нужно понять, как мы будем создавать и хранить токен. Для этого, нам нужно создать экземпляр `EmailLogin`, так что приступим:
public function login()
{
$this->validate($request, ['email' => 'required|email|exists:users']);
$emailLogin = EmailLogin::createForEmail($request->input('email'));
}
Давайте добавим этот метод в `EmailLogin`:
class EmailLogin extends Model
{
...
public static function createForEmail($email)
{
return self::create([
'email' => $email,
'token' => str_random(20)
]);
}
}
Мы генерируем рандомный токен и создаем экземпляр класса `EmailToken`, получая его обратно.
Формирование URL для отправки по email
Итак, нам нужно использовать `EmailToken` для формирования URL перед отправкой сообщения пользователю.
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
]);
}
Давайте создадим для него роут:
// app/Http/routes.php
Route::get('auth/email-authenticate/{token}', [
'as' => 'auth.email-authenticate',
'uses' => 'Auth\AuthController@authenticateEmail'
]);
… и добавим метод в контроллер для работы этого маршрута:
class AuthController
{
...
public function authenticateEmail($token)
{
$emailLogin = EmailLogin::validFromToken($token);
Auth::login($emailLogin->user);
return redirect('home');
}
}
… и еще добавим метод `validFromToken` для проверки токена:
class EmailLogin
{
...
public static function validFromToken($token)
{
return self::where('token', $token)
->where('created_at', '>', Carbon::parse('-15 minutes'))
->firstOrFail();
}
Теперь у нас есть входящий роут, учитывающий актуальность каждого токена. Если токен актуален — пользователь будет перенаправлен по адресу `mysite.ru/home`.
Что ж, давайте отправим письмо.
Отправка письма
Добавим вызов `call email` в наш контроллер:
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');
});
… и создадим шаблон:
Log in to MyApp here: {{ $url }}
Возвращение шаблона
Вы можете оформить шаблон любым удобным способом, но мы просто используем текст: «Эй, мы отправили мыло, чтобы его проверить. Это все.»
return 'Login email sent. Go check your email.';
Совместный вход
Взглянем на нашу систему. У нас есть новый метод `login` в контроллере `AuthController`:
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.';
}
Мы создали несколько вьюх, обновив существующие (убрали в них записи о пароле). Также создали новый роут в `/auth/email-authenticate`. И также создали миграцию `EmailLogin` с классом всех его потребностей.
Это все!
И… профит! Поместите все примеры в Ваш код и получите полностью функциональную систему беспарольного входа.
Для регистрации пользователя нужно будет узнать всего-лишь их email-адрес. И при авторизации кроме их email-адреса больше ничего не нужно будет запоминать и вводить. Больше нет забытых паролей. Бум!
От переводчика
При переводе статьи была произведена адаптация информации для лучшей читаемости русскоговорящих пользователей.