Создание беспарольной аутентификации в Laravel, используя только email

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

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

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

Начнем творить!

image


Новое приложение и 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');
    });


… и создадим шаблон:

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


Возвращение шаблона


Вы можете оформить шаблон любым удобным способом, но мы просто используем текст: «Эй, мы отправили мыло, чтобы его проверить. Это все.»

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-адреса больше ничего не нужно будет запоминать и вводить. Больше нет забытых паролей. Бум!

От переводчика


При переводе статьи была произведена адаптация информации для лучшей читаемости русскоговорящих пользователей.
  • +7
  • 11.6k
  • 9
Share post

Similar posts

Comments 9

    +5
    Мне, как пользователю проще придумать пароль и моментально авторизоваться, чем лезть в почту, искать ссылку и переходить по ней.
      0
      а иногда почта и вовсе не доходит…
      даже такой гигант как paypal регулярно меня подводит с mail… так что я всё же не стал бы делать это основным способом авторизации…
      а вот регистрация указав только лишь email — это уже другое дело…
      ввел почту нажал кнопку и ты уже на сайте, пароль на почте всё остальное юзер когда захочет отредактирует =)

      не люблю долгие регистрации...
        –1
        Учитывая то, что автор статьи не из России, вестимо, у них все же это лучший вариант был.

        В нашей же стране согласен в пользу паролей.
        0
        Кажется что в совмеменном интернете самое простое — это авторизация через крупные социальные сети. Для тех у кого нет аккаунтов — оставить старый добрый вход по логину и паролю. Хотя и этот метод не плох, неясна только область применения… типа пробной регистрации, чтобы пользователь мог оценить, нужна ли ему постоянная регистрация или нет?
          0
          То есть вы хотите, чтобы любой сайт моментально получал на вас полное досье?
            0
            У того же VK API при авторизации базово система возвращает имя, дату рождения, мыло и основную информацию, доступную любому пользователю.
            Для передачи "досье" человек сам должен будет подтвердить этот факт, или не юзать такой ресурс.

            Думаю, NeoCode имеет ввиду простоту авторизации, юзая соцсети.

            Кстати, я с ним согласен. Вхожу на разные сайты используя как раз соцсети — быстро и удобно.
          +1
          Я думаю, что это палка двух концов. С одной стороны удобно ввел мейл и забыл про все, с другой стороны каждый раз лезть в почту и переходить по ссылке. Для нашей страны это станет удобно не в ближайшее время)
            0
            Не знаю как у всех, но у меня уже давно "лезть в почту" — это переключиться в соседний таб. Но тем не менее я согласен, что подобный метод авторизации так себе.
              0
              На работе используют систему "Slack" для общения. На форме входа в эту систему увидел 2 кнопки:
              Скрин свернут
              image

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

            Only users with full accounts can post comments. Log in, please.