• Czas czytania ~5 min
  • 21.03.2023

Czasami nie chcemy, aby użytkownicy mieli hasła. Czasami chcemy wysłać magiczny link na adres e-mail użytkownika i poprosić go o kliknięcie, aby uzyskać dostęp.

W tym samouczku przejdę przez proces, którego możesz użyć do samodzielnego wdrożenia. Głównym celem tego przepływu pracy jest utworzenie podpisanego adresu URL, który umożliwi nam wysłanie określonego adresu URL na adres e-mail użytkowników i tylko ta osoba powinna mieć dostęp do tego adresu URL.

Najpierw chcemy usunąć pole hasła z naszej fabryki migracji, modelu i modelu. Ponieważ nie będzie to potrzebne, chcemy mieć pewność, że go usuniemy, ponieważ domyślnie nie jest to kolumna z wartością null. Jest to stosunkowo prosty proces do osiągnięcia, więc nie pokażę żadnych przykładów kodu dla tej części. Gdy już przy tym jesteśmy, możemy usunąć tabelę resetowania hasła, ponieważ nie będziemy mieli hasła do zresetowania.

Wyznaczanie tras powinno być następną rzeczą, której się przyjrzymy. Możemy utworzyć naszą trasę logowania jako prostą trasę widoku, ponieważ w tym przykładzie użyjemy Livewire. Rzućmy okiem na rejestrację tej trasy: Chcemy owinąć to w oprogramowanie pośredniczące gościa,

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

aby wymusić przekierowanie, jeśli użytkownik jest już zalogowany. Nie przejdę przez interfejs użytkownika w tym przykładzie, ale na końcu samouczka znajduje się link do repozytorium na GitHub. Przejdźmy przez komponent Livewire, którego użyjemy do formularza logowania.

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

Nasz komponent ma dwie właściwości, które będziemy chcieli wykorzystać. Wiadomość e-mail służy do przechwytywania danych wejściowych formularza. Następnie status jest następujący, więc nie musimy polegać na sesji żądania. Mamy metodę, która zwraca reguły sprawdzania poprawności. Jest to moje preferowane podejście do reguł walidacji w komponencie Livewire. Nasza metoda przesyłania jest podstawową metodą dla tego składnika i jest to konwencja nazewnictwa, której używam podczas pracy ze składnikami formularza. Ma to dla mnie sens, ale możesz wybrać metodę nazewnictwa, która działa dla Ciebie. Używamy kontenera Laravels, aby wstrzyknąć klasę akcji do tej metody, aby udostępnić logikę tworzenia i wysyłania podpisanego adresu URL. Wszystko, co musimy tutaj zrobić, to przekazać wprowadzony e-mail do akcji i ustawić status ostrzegający użytkownika, że wiadomość e-mail jest wysyłana.

Przejdźmy teraz do akcji, której chcemy użyć.

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

Ta czynność wymaga tylko wysłania wiadomości e-mail. Możemy skonfigurować to tak, aby było kolejkowane, jeśli chcemy - ale gdy mamy do czynienia z akcją wymagającą szybkiego przetwarzania, lepiej ją kolejkować, jeśli budujemy API. Mamy klasę mailingową o nazwie LoginLink , którą przekazujemy przez adres URL, którego chcemy użyć. Nasz adres URL jest tworzony przez przekazanie nazwy trasy, dla której chcemy wygenerować trasę i przekazanie parametrów, których chcesz użyć w ramach podpisywania.

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 [];
    }
}

Nasza klasa mailable jest stosunkowo prosta i nie odbiega zbytnio od standardowej mailable. Przekazujemy ciąg znaków dla adresu URL. Następnie chcemy przekazać to do widoku znaczników w treści.

<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>

Użytkownik otrzyma tę wiadomość e-mail i kliknie link, przenosząc go do podpisanego adresu URL. Zarejestrujmy tę trasę i zobaczmy, jak ona wygląda.

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

Chcemy użyć kontrolera dla tej trasy i upewnić się, że dodajemy podpisane oprogramowanie pośredniczące. Teraz spójrzmy na kontroler, aby zobaczyć, jak obsługujemy podpisane adresy 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'),
        );
    }
}

Naszym pierwszym krokiem jest upewnienie się, że adres URL ma prawidłowy podpis, a jeśli nie, chcemy wysłać nieautoryzowaną odpowiedź. Gdy wiemy, że podpis jest prawidłowy, możemy zapytać o przekazanego użytkownika i uwierzytelnić go. Na koniec zwracamy przekierowanie do pulpitu nawigacyjnego.

Nasz użytkownik jest teraz pomyślnie zalogowany, a nasza podróż jest zakończona. Musimy jednak przyjrzeć się również ścieżce rejestracji. Dodajmy tę trasę dalej. Ponownie będzie to trasa widokowa.

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

Ponownie, używamy komponentu livewire do formularza rejestracyjnego - tak jak zrobiliśmy to z procesem logowania.

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

Przechwytujemy nazwę i adres e-mail użytkowników oraz właściwość status zamiast ponownie używać sesji żądania. Ponownie używamy metody reguł, aby zwrócić reguły sprawdzania poprawności dla tego żądania. Wracamy do metody przesyłania, gdzie tym razem chcemy wstrzyknąć dwie akcje.

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,
        ]);
    }
}

Moglibyśmy zmienić nazwę trasy sklepu logowania, ale technicznie jest to to, co robimy ponownie. Tworzymy użytkownika. Następnie chcemy zalogować użytkownika.

Jest to jedno z wielu podejść, które można zastosować w celu zaimplementowania uwierzytelniania bez hasła, ale jest to jedno podejście, które działa. Możesz znaleźć repozytorium GitHub tutaj, a jeśli uważasz, że można to poprawić, możesz rzucić PR!

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

O

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...

O autorze CrazyBoy49z
WORK EXPERIENCE
Kontakt
Ukraine, Lutsk
+380979856297