Posted on: March 21, 2023 11:22 AM
Posted by: Renato
Categories: Laravel Passwordless authentication Dicas
Views: 424
Autenticação sem palavra-passe em Laravel
...
Por vezes não queremos que os utilizadores tenham senhas. Por vezes, queremos enviar um link mágico para o endereço de correio electrónico de um utilizador e pedir-lhes que cliquem para obter acesso.
Neste tutorial, caminharei através de um processo que poderá utilizar para implementar isto por si próprio. O foco principal deste fluxo de trabalho é criar um URL assinado que nos permitirá enviar um URL específico para o endereço de correio electrónico dos utilizadores, e apenas essa pessoa deverá poder aceder a este URL.
Primeiro queremos remover o campo de senha da nossa migração, modelo, e fábrica modelo. Como isto não será necessário, queremos assegurar a sua remoção, uma vez que não é uma coluna nula por defeito. Este é um processo relativamente simples de conseguir, pelo que não mostrarei quaisquer exemplos de código para esta parte. Já que estamos a fazê-lo, podemos remover a tabela de reinicialização de senhas, uma vez que não teremos uma senha para reinicializar.
O encaminhamento deve ser a próxima coisa que devemos observar. Podemos criar a nossa rota de login como uma rota de visualização simples, uma vez que utilizaremos o Livewire para este exemplo. Vamos dar uma vista de olhos ao registo desta rota:
Route::middleware(['guest'])->group(static function (): void {
Route::view('login', 'app.auth.login')->name('login');
});
.
Queremos embrulhar isto no middleware convidado para forçar um redireccionamento se o utilizador já estiver logado. Não vou passar pela IU para este exemplo, mas no final do tutorial, existe um link para o redireccionamento no GitHub. Vamos percorrer a componente Livewire que iremos utilizar para o formulário de login.
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');
}
}
.
O nosso componente tem duas propriedades que vamos querer utilizar. O e-mail é utilizado para capturar a entrada do formulário. Depois o estado é, por isso não precisamos de confiar na sessão de pedido. Temos um método que nos devolve as regras de validação. Esta é a minha abordagem preferida para as regras de validação num componente Livewire. O nosso método de submissão é o principal método para este componente, e é uma convenção de nomenclatura que utilizo quando lido com componentes de formulários. Isto faz muito sentido para mim, mas sinta-se à vontade para escolher um método de nomenclatura que funcione para si. Utilizamos o recipiente de Laravels para injectar uma classe de acção neste método para partilhar a lógica de criação e envio de um URL assinado. Tudo o que precisamos de fazer aqui é passar o e-mail introduzido para a acção e definir um estado alertando o utilizador de que o e-mail está a ser enviado.
Vamos agora caminhar através da acção que queremos utilizar.
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,
),
)
);
}
}
.
Esta acção só precisa de enviar um e-mail. Podemos configurar isto para ser enfileirado se quisermos - mas quando lidamos com uma acção que requer processamento rápido, é melhor enfileirá-lo se estivermos a construir um API. Temos uma classe de correio chamado LoginLink que passamos através do URL que queremos utilizar. O nosso URL é criado passando em nome de uma rota que queremos gerar uma rota e passando os parâmetros que queremos utilizar como parte da assinatura.
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 [];
}
}
.
Nossa classe de envio por correio é relativamente direta e não difere muito de um envio por correio padrão. Passamos uma string para o URL. Em seguida, queremos passar isso para uma exibição de remarcação no conteúdo.
<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>
.
O usuário receberá este e-mail e clicará no link, levando-o até a URL assinada. Vamos registrar esta rota e ver como fica.
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');
});
.
Queremos usar um controlador para esta rota e garantir que adicionamos o middleware assinado. Agora vamos olhar para o controlador para ver como lidamos com URLs assinados.
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'),
);
}
}
O nosso primeiro passo é assegurar que o URL tem uma assinatura válida, e se não tiver, queremos lançar uma resposta não autorizada. Assim que soubermos que a assinatura é válida, podemos consultar o utilizador que passou por ela e autenticar a sua autenticação. Finalmente, devolvemos um redireccionamento para o painel de bordo.
O nosso utilizador está agora conectado com sucesso, e a nossa viagem está completa. No entanto, precisamos de olhar também para a rota de registo. Acrescentemos este itinerário a seguir. Mais uma vez, esta será uma rota de visualização.
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');
});
Novamente, usamos um componente livewire para o formulário de registro - assim como fizemos com o processo de login.
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');
}
}
Capturamos o nome dos utilizadores, endereço de correio electrónico, e temos uma propriedade de status em vez de utilizarmos novamente a sessão de pedido. Mais uma vez, utilizamos um método de regras para devolver as regras de validação para este pedido. Voltamos ao método de submissão, onde desta vez, queremos injectar duas acções.
'CreateNewUser' é a acção que utilizamos para criar e devolver um novo utilizador com base na informação fornecida. Se isto falhar por alguma razão, lançamos uma excepção de validação no e-mail. Depois utilizamos a acção 'SendLoginLink' que utilizámos no formulário de login para minimizar a duplicação de código.
final class CreateNewUser
{
public function handle(string $name, string $email): Builder|Model
{
return User::query()->create([
'name' => $name,
'email' => $email,
]);
}
}
Poderíamos renomear a rota da loja de login, mas tecnicamente é o que estamos a fazer novamente. Criamos um utilizador. Depois queremos fazer o login do utilizador.
Esta é uma das muitas abordagens que pode adoptar para implementar a autenticação sem palavra-passe, mas esta é uma abordagem que funciona. Pode encontrar o GitHub Repo aqui link-repo, e se pensa que isto pode ser melhorado, sinta-se à vontade para deixar cair um PR!
Fonte:
https://github.com/lucenarenato/passwordless-auth
https://laravel-news.com/passwordless-authentication-in-laravel
Donate to Site
Renato
Developer