• Час читання ~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. Наш метод подання є основним методом для цього компонента, і це угода про найменування, яку я використовую, коли маю справу з компонентами форми. Для мене це має великий сенс, але сміливо вибирайте метод іменування, який підходить саме вам. Ми використовуємо контейнер 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. У нас є клас, який можна надіслати поштою, який 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-адреси. Потім ми хочемо передати це до перегляду розмітки у вмісті.

<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');
    }
}

Ми фіксуємо ім'я користувача, адресу електронної пошти та маємо властивість статусу замість того, щоб знову використовувати сеанс запиту. Знову ж таки, ми використовуємо метод правил, щоб повернути правила перевірки для цього запиту. Повертаємося до методу подачі, де на цей раз хочемо ввести дві дії.

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 тут, і якщо ви думаєте, що це можна покращити, сміливо кидайте піар!

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