Иногда мы не хотим, чтобы у пользователей были пароли. Иногда мы хотим отправить волшебную ссылку на адрес электронной почты пользователя и попросить его щелкнуть, чтобы получить доступ.
В этом уроке я расскажу о процессе, который вы можете использовать для реализации этого самостоятельно. Основное внимание в этом рабочем процессе уделяется созданию подписанного 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!