{{TOC}} {{Image /packages/proger/habravel/uploads/353-log-in-now-838x275.png, height=100px}} Недавно я работал над проектом, где одной из главных трудностей были пользовательские пароли. Пользователей завели в систему администраторы, поэтому для них не были заданы пароли. А если бы пришлось вынуждать их вводить и запоминать пароли, то это серьёзно снизило бы юзабилити проекта. Поэтому мы решили попробовать беспарольный вход в стиле Medium/Slack. Если вы с таким не сталкивались, то в двух словах это работает так: вводите свой email на странице входа в систему, получаете письмо со ссылкой на вход, щёлкаете по ссылке и входите в систему. Доступ к вашему адресу электронной почты удостоверяет вашу личность не требуя пароля. Давайте вместе сделаем такой вход. == Новое приложение и make:auth == Первым делом создадим наше laravel-приложение и сгенерируем систему аутентификации: %%(sh) laravel new medium-login cd medium-login php artisan make:auth %% Теперь у нас есть ряд новых файлов, связанных с аутентификацией, включая страницы входа и регистрации. Давайте начнём с настройки этих файлов. == Изменение страниц входа и регистрации == {{Image /packages/proger/habravel/uploads/353-no-password-358x352.png, height=150px}} Страницы входа в систему и регистрации довольно хороши, но нам надо убрать из них поле пароля. Откройте страницу входа %%(t)resources/views/auth/login.blade.php%% и удалите всю группу формы %%(t)password%% (метки, поля ввода и обёртку %%(html)
%%). Сохраните и закройте. Откройте страницу регистрации %%(t)resources/views/auth/register.blade.php%% и также удалите группы формы %%(t)password%% и %%(t)password-reset%%. Сохраните и закройте. Возможно, позже вы захотите добавить некоторые инструкции на обеих страницах, описывающие, как будет работать наша аутентификация, а также добавить ссылки для сброса пароля. Но пока этого будет вполне достаточно. == Изменение маршрутов регистрации == Теперь нам нужно обновить маршрут, на который указывают формы входа и регистрации. Давайте перейдём к %%(t)AuthController%% и посмотрим, что у нас есть. Во-первых, мы заметим метод %%(t)validator%%, который возвращает валидатор для поля %%(t)password%%. Это валидатор для регистрации учётной записи, так что давайте избавимся от этого пароля. В конечном итоге функция должна выглядеть так: %% // 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', ]); } %% И сделаем то же самое для метода %%(t)create%%, который также используется для регистрации: %% // app/http/Controllers/Auth/AuthController.php protected function create(array $data) { return User::create([ 'name' => $data['name'], 'email' => $data['email'], ]); } %% == Переопределение маршрута %%(t)login%% == Как вы могли заметить, здесь нет методов для входа пользователей. Они скрыты в типаже %%(t)AuthenticatesAndRegistersUsers%%, который просто выдает вам типажи %%(t)AuthenticatesUsers%% и %%(t)RegistersUsers%%. Вы можете перейти к типажу %%(t)AuthenticatesUsers%% и найти в нём метод %%(t)login%%, который и позволяет входить пользователям в систему. Всё, что там происходит, основано на входе с использованием пароля. Поэтому давайте переопределим этот метод до самого основания. Целью нашего нового метода будет отправка пользователю письма и вывод подсказки о необходимости проверить почту. Давайте перейдем к %%(t)AuthController%% и добавим метод %%(t)login%%, чтобы переопределить тот, который есть в %%(t)AuthenticatesUsers%%. %% // app/http/Controllers/Auth/AuthController.php public function login(Request $request) { // проверяем, что это реальный email, // отправляем письмо для входа, // показываем пользователю представление с текстом "проверьте почту". } %% == Проверка email == Сначала давайте проверим адрес email пользователя. Это довольно просто: %% $this->validate($request, ['email' => 'required|email|exists:users']); %% == Отправка письма для входа в систему == Затем нам надо послать письмо на указанный адрес и пригласить пользователя войти. Тут потребуется немного больше усилий. === Создание структуры для генерации и проверки почтовых токенов входа === Если вы знакомы со структурой базы данных %%(t)password_reset%%, вам повезло. Мы будем создавать что-то очень похожее. Каждый раз, когда кто-то пытается войти в систему, мы должны будем добавить в таблицу запись, включающую адрес электронной почты, только что созданный уникальный токен (его мы отправим в электронном письме как часть URL), и дату создания записи для отслеживания срока действия ссылки. Впоследствии мы будем использовать эту запись, чтобы генерировать (и проверять) вот такие URL: %%(t)myapp.com/auth/email-authenticate/09ajfpoib23li4ub123p984h1234%%. Вход в систему по данной ссылке истечёт через заданный промежуток времени. Нам нужно будет связать данный URL с определённым пользователем, таким образом, мы должны зафиксировать %%(t)email%%, %%(t)token%% и %%(t)created_at%% для каждой записи в этой таблице. Давайте создадим миграцию для неё: %%(sh) 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(); }); %% .(alert) Если хотите, вы можете использовать столбец %%(t)id%% внешнего ключа вместо %%(t)email%%. Есть несколько причин, по которым использовать его лучше, чем просто email. Но мне нравится просто %%(t)email%%. Решать вам. Теперь давайте создадим модель. %%(sh) php artisan make:model EmailLogin %% Отредактируйте файл (%%(t)app/EmailLogin.php%%) так, чтобы нам можно было просто создать экземпляр с нужными свойствами: %% class EmailLogin extends Model { public $fillable = ['email', 'token']; } %% И затем мы захотим связать каждый экземпляр с пользователем. А поскольку в нашем примере мы отслеживаем пользователя по %%(t)email%%, а не по %%(t)id%%, мы должны вручную привязать столбец таблицы %%(t)email%%: %% class EmailLogin extends Model { public $fillable = ['email', 'token']; public function user() { return $this->hasOne(\App\User::class, 'email', 'email'); } } %% === Создание токена === Теперь можно создать письмо. Мы собираемся отправить конечному пользователю письмо с URL, в котором будет сгенерированный ранее уникальный токен. Сначала давайте определимся, каким образом мы будем создавать и хранить эти токены. Нам нужно создать экземпляр %%(t)EmailLogin%%, с этого и начнём: %% public function login() { $this->validate($request, ['email' => 'required|email|exists:users']); $emailLogin = EmailLogin::createForEmail($request->input('email')); } %% Давайте добавим этот метод в %%(t)EmailLogin%%: %% class EmailLogin extends Model { ... public static function createForEmail($email) { return self::create([ 'email' => $email, 'token' => str_random(20) ]); } } %% Теперь мы создаём экземпляр класса %%(t)EmailLogin%%, в котором генерируем рандомный токен, после чего возвращаем этот экземпляр обратно. === Создание URL для отправки на email === Теперь нам надо использовать этот %%(t)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'); } } %% ... и давайте создадим метод %%(t)validFromToken%%: %% class EmailLogin { ... public static function validFromToken($token) { return self::where('token', $token) ->where('created_at', '>', Carbon::parse('-15 minutes')) ->firstOrFail(); } %% Теперь у нас есть входящий маршрут с проверенным непросроченным токеном, который вводит пользователя и перенаправляет его к %%(t)home%%. Давайте отправим это письмо. === Отправка 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'); }); %% ... и создадим шаблон письма: %%(html) Войти в систему по этой ссылке: {{ $url }} %% === Возврат временного представления === Вы можете создать представление каким угодно образом, но в целом нам просто нужно сообщить пользователю "Эй, мы послали тебе письмо, загляни в свой почтовый ящик". И всё. %% return 'Письмо для входа в систему отправлено. Проверьте свою электронную почту.'; %% == Соединение всего воедино == Давайте взглянем на нашу систему. У нас есть новый метод %%(t)login%% в нашем %%(t)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 'Письмо для входа в систему отправлено. Проверьте свою электронную почту.'; } %% Мы создали несколько новых представлений. Мы обновили старые представления - убрали поля паролей. Мы создали новый маршрут в %%(t)/auth/email-authenticate%%. И мы создали миграцию %%(t)EmailLogin%% и класс для всего этого. == Вот и всё! == Готово! Соедините все эти части, и у вас получится полностью функциональная, беспарольная система входа. Когда ваши пользователи регистрируются в системе, они должны ввести только свой адрес электронной почты. Когда ваши пользователи заходят в систему, они должны ввести только свой адрес электронной почты. Теперь не будет никаких забытых паролей. Вуаля!