Por Marco Orta
Soy Marco Orta, un desarrollador web y diseñador gráfico con más de 10 años de experiencia en el desarrollo de sitios web y aplicaciones web.
A veces no queremos que los usuarios tengan contraseñas. En ocasiones, queremos enviar un enlace mágico al correo electrónico del usuario y que hagan clic para obtener acceso.
En este tutorial, te guiaré a través de un proceso que puedes usar para implementarlo tú mismo. El enfoque principal de este flujo de trabajo es crear una URL firmada que nos permitirá enviar una URL específica al correo electrónico de los usuarios, y solo esa persona debería poder acceder a esta URL.
Primero queremos eliminar el campo de contraseña de nuestra migración, modelo y fábrica de modelos. Como no será necesario, queremos asegurarnos de eliminarlo, ya que no es una columna que admita nulos por defecto. Este es un proceso relativamente sencillo de lograr, así que no mostraré ejemplos de código para esta parte. Mientras estamos en ello, también podemos eliminar la tabla de restablecimientos de contraseña, ya que no tendremos una contraseña para restablecer.
La siguiente cosa que deberíamos mirar es el enrutamiento. Podemos crear nuestra ruta de inicio de sesión como una ruta de vista simple, ya que usaremos Livewire para este ejemplo. Echemos un vistazo a cómo registrar esta ruta:
Route::middleware(['guest'])->group(static function (): void {
Route::view('login', 'app.auth.login')->name('login');
});
Queremos envolver esto en el middleware de invitado para forzar una redirección si el usuario ya está conectado. No revisaré la interfaz de usuario para este ejemplo, pero al final del tutorial, hay un enlace al repositorio en GitHub. Vamos a revisar el componente Livewire que usaremos para el formulario de inicio de sesión.
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 = 'Se ha enviado un correo electrónico para que puedas iniciar sesión.';
}
public function rules(): array
{
return [
'email' => [
'required',
'email',
Rule::exists(
table: 'users',
column: 'email',
),
]
];
}
public function render(): View
{
return view('livewire.auth.login-form');
}
}
Nuestro componente tiene dos propiedades que querremos usar. El correo electrónico se usa para capturar la entrada del formulario. Luego, el estado es para no tener que depender de la sesión de la solicitud. Tenemos un método que devuelve las reglas de validación. Este es mi enfoque preferido para las reglas de validación en un componente de Livewire. Nuestro método de envío es el método principal para este componente, y es una convención de nombres que utilizo cuando trato con componentes de formulario. Esto tiene mucho sentido para mí, pero siéntete libre de elegir un método de denominación que funcione para ti. Usamos el contenedor de Laravel para inyectar una clase de acción en este método para compartir la lógica de creación y envío de una URL firmada. Todo lo que necesitamos hacer aquí es pasar el correo electrónico ingresado a la acción y establecer una alerta de estado informando al usuario que el correo electrónico está siendo enviado.
Ahora, revisemos la acción que queremos usar.
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 acción solo necesita enviar un correo electrónico. Podemos configurar esto para que se encole si queremos, pero cuando se trata de una acción que requiere un procesamiento rápido, es mejor encolarla si estamos construyendo una API. Tenemos una clase mailable llamada LoginLink a la que pasamos la URL que queremos usar. Nuestra URL se crea pasando el nombre de una ruta para la que queremos generar una ruta y pasando los parámetros que quieres usar como parte de la firma.
final class LoginLink extends Mailable
{
use Queueable, SerializesModels;
public function __construct(
public readonly string $url,
) {}
public function envelope(): Envelope
{
return new Envelope(
subject: 'Tu enlace mágico esta aquí!',
);
}
public function content(): Content
{
return new Content(
markdown: 'emails.auth.login-link',
with: [
'url' => $this->url,
],
);
}
public function attachments(): array
{
return [];
}
}
Nuestra clase mailable es relativamente sencilla y no difiere mucho de un mailable estándar. Pasamos una cadena para la URL. Luego, queremos pasar esto a una vista markdown en el contenido.
<:message>
# Login Link
Usa el siguiente enlace para iniciar sesión en {{ config('app.name') }}.
<:button :url="$url">
Iniciar Sesión
Gracias,
{{ config('app.name') }}
El usuario recibirá este correo electrónico y hará clic en el enlace, llevándolos a la URL firmada. Registremos esta ruta y veamos cómo se ve.
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 un controlador para esta ruta y asegurarnos de añadir el middleware firmado. Ahora veamos el controlador para ver cómo manejamos las URLs firmadas.
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'),
);
}
}
Nuestro primer paso es asegurarnos de que la URL tenga una firma válida y, si no la tiene, queremos lanzar una respuesta no autorizada. Una vez que sabemos que la firma es válida, podemos consultar al usuario pasado y autenticarlo. Finalmente, devolvemos una redirección al panel de control.
Nuestro usuario ahora ha iniciado sesión con éxito, y nuestro recorrido está completo. Sin embargo, también necesitamos mirar la ruta de registro. Añadamos esta ruta a continuación. Nuevamente, será una ruta de vista.
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');
});
De nuevo, utilizamos un componente Livewire para el formulario de registro, al igual que hicimos con el proceso de inicio de sesión.
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' => 'Ocurrió un error, por favor intentelo nuevamente.',
],
);
}
$action->handle(
email: $this->email,
);
$this->status = 'Te hemos enviado un email para que puedas iniciar sesión.';
}
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 el nombre y la dirección de correo electrónico del usuario y tenemos una propiedad de estado en lugar de utilizar nuevamente la sesión de solicitud. Nuevamente utilizamos un método de reglas para devolver las reglas de validación para esta solicitud. Regresamos al método de envío, donde esta vez, queremos inyectar dos acciones.
CreateNewUser es la acción que utilizamos para crear y devolver un nuevo usuario basado en la información proporcionada. Si esto falla por alguna razón, lanzamos una excepción de validación en el correo electrónico. Luego usamos la acción SendLoginLink que utilizamos en el formulario de inicio de sesión para minimizar la duplicación de código.
final class CreateNewUser
{
public function handle(string $name, string $email): Builder|Model
{
return User::query()->create([
'name' => $name,
'email' => $email,
]);
}
}
Podríamos renombrar la ruta de almacenamiento de inicio de sesión, pero técnicamente es lo que estamos haciendo de nuevo. Creamos un usuario. Luego queremos iniciar sesión con el usuario.
Este es uno de los muchos enfoques que puedes tomar para implementar la autenticación sin contraseña, pero este es un enfoque que funciona.
Deja un Comentario
Tu dirección de correo electrónico no será publicada.