• Время чтения ~6 мин
  • 21.03.2023

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

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

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

Маршрутизация должна быть следующей вещью, на которую мы смотрим. Мы можем создать наш маршрут входа в систему как простой маршрут просмотра, так как мы будем использовать Livewire для этого примера. Давайте посмотрим на регистрацию этого маршрута:

Route::middleware(['guest'])->group(static function (): void {
    Route::view('login', 'app.auth.login')->name('login');
});

Мы хотим обернуть его в гостевое промежуточное ПО, чтобы принудительно перенаправить, если пользователь уже вошел в систему. Я не буду рассматривать пользовательский интерфейс для этого примера, но в конце учебника есть ссылка на репозиторий на GitHub. Давайте рассмотрим компонент Livewire, который мы будем использовать для формы входа.

final class LoginForm extends Component
{
    public string $email = '';

    public string $status = '';

    public function submit(SendLoginLink $action): void
    {
        $this->validate();

        $action->handle(
            email: $this->email,
        );
        $this->status = 'An email has been sent for you to log in.';
    }
    public function rules(): array
    {
        return [
            'email' => [
                'required',
                'email',
                Rule::exists(
                    table: 'users',
                    column: 'email',
                ),
            ]
        ];
    }
    public function render(): View
    {
        return view('livewire.auth.login-form');
    }
}

Наш компонент имеет два свойства, которые мы захотим использовать. Сообщение электронной почты используется для записи входных данных формы. Тогда состояние есть, поэтому нам не нужно полагаться на сеанс запроса. У нас есть метод, который возвращает правила проверки. Это мой предпочтительный подход к правилам проверки в компоненте Livewire. Наш метод submit является основным методом для этого компонента, и это соглашение об именовании, которое я использую при работе с компонентами формы. Это имеет большой смысл для меня, но не стесняйтесь выбирать метод именования, который работает для вас. Мы используем контейнер Laravels для внедрения класса действия в этот метод, чтобы поделиться логикой создания и отправки подписанного URL-адреса. Все, что нам нужно сделать здесь, это передать введенное электронное письмо действию и установить статус, предупреждающий пользователя о том, что электронное письмо отправляется.

Давайте теперь рассмотрим действие, которое мы хотим использовать.

final class SendLoginLink
{
    public function handle(string $email): void
    {
        Mail::to(
            users: $email,
        )->send(
            mailable: new LoginLink(
                url: URL::temporarySignedRoute(
                    name: 'login:store',
                    parameters: [
                        'email' => $email,
                    ],
                    expiration: 3600,
                ),
            )
        );
    }
}

Для этого действия нужно только отправить электронное письмо. Мы можем настроить его для постановки в очередь, если захотим, но при работе с действием, требующим быстрой обработки, лучше поставить его в очередь, если мы создаем API. У нас есть класс mailable, LoginLink который мы передаем через URL-адрес, который мы хотим использовать. Наш URL-адрес создается путем передачи имени маршрута, для которого мы хотим создать маршрут, и передачи параметров, которые вы хотите использовать как часть подписи.

final class LoginLink extends Mailable
{
    use Queueable, SerializesModels;

    public function __construct(
        public readonly string $url,
    ) {}
    public function envelope(): Envelope
    {
        return new Envelope(
            subject: 'Your Magic Link is here!',
        );
    }
    public function content(): Content
    {
        return new Content(
            markdown: 'emails.auth.login-link',
            with: [
                'url' => $this->url,
            ],
        );
    }
    public function attachments(): array
    {
        return [];
    }
}

Наш почтовый класс относительно прост и не сильно отличается от стандартной почтовой рассылки. Мы передаем строку для URL- адреса. Затем мы хотим передать это в представление markdown в контенте.

<x-mail::message>
# Login Link
Use the link below to log into the {{ config('app.name') }} application.
<x-mail::button :url="$url">
Login
</x-mail::button>

Thanks,<br>
{{ config('app.name') }}
</x-mail::message>

Пользователь получит это письмо и нажмет на ссылку, перейдя по подписанному URL-адресу. Давайте зарегистрируем этот маршрут и посмотрим, как он выглядит.

Route::middleware(['guest'])->group(static function (): void {
    Route::view('login', 'app.auth.login')->name('login');
    Route::get(
        'login/{email}',
        LoginController::class,
    )->middleware('signed')->name('login:store');
});

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

final class LoginController
{
    public function __invoke(Request $request, string $email): RedirectResponse
    {
        if (! $request->hasValidSignature()) {
            abort(Response::HTTP_UNAUTHORIZED);
        }
        /**
         * @var User $user
         */
        $user = User::query()->where('email', $email)->firstOrFail();

        Auth::login($user);

        return new RedirectResponse(
            url: route('dashboard:show'),
        );
    }
}

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

Теперь наш пользователь успешно вошел в систему, и наше путешествие завершено. Тем не менее, нам нужно посмотреть и на маршрут регистрации. Давайте добавим этот маршрут следующим. Опять же, это будет маршрут просмотра.

Route::middleware(['guest'])->group(static function (): void {
    Route::view('login', 'app.auth.login')->name('login');
    Route::get(
        'login/{email}',
        LoginController::class,
    )->middleware('signed')->name('login:store');

    Route::view('register', 'app.auth.register')->name('register');
});

Опять же, мы используем компонент livewire для регистрационной формы - так же, как мы сделали с процессом входа в систему.

final class RegisterForm extends Component
{
    public string $name = '';

    public string $email = '';

    public string $status = '';

    public function submit(CreateNewUser $user, SendLoginLink $action): void
    {
        $this->validate();

        $user = $user->handle(
            name: $this->name,
            email: $this->email,
        );
        if (! $user) {
            throw ValidationException::withMessages(
                messages: [
                    'email' => 'Something went wrong, please try again later.',
                ],
            );
        }
        $action->handle(
            email: $this->email,
        );
        $this->status = 'An email has been sent for you to log in.';
    }
    public function rules(): array
    {
        return [
            'name' => [
                'required',
                'string',
                'min:2',
                'max:55',
            ],
            'email' => [
                'required',
                'email',
            ]
        ];
    }
    public function render(): View
    {
        return view('livewire.auth.register-form');
    }
}

Мы записываем имя пользователя, адрес электронной почты и имеем свойство status вместо повторного использования сеанса запроса. Опять же, мы используем метод rules для возврата правил проверки для этого запроса. Мы возвращаемся к методу submit, где на этот раз мы хотим ввести два действия.

CreateNewUser is the action we use to create and return a new user based on the information provided. If this fails for some reason, we throw a validation exception on the email. Then we use the SendLoginLink action we used on the login form to minimize code duplication.

final class CreateNewUser
{
    public function handle(string $name, string $email): Builder|Model
    {
        return User::query()->create([
            'name' => $name,
            'email' => $email,
        ]);
    }
}

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

Это один из многих подходов, которые вы можете использовать для реализации аутентификации без пароля, но это один из подходов, который работает. Вы можете найти репозиторий GitHub здесь, и если вы думаете, что это можно улучшить, не стесняйтесь бросать PR!

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Об авторе CrazyBoy49z
WORK EXPERIENCE
Контакты
Ukraine, Lutsk
+380979856297