Hoy vamos a ver como hacer un sistema de autenticación sin contraseña en Laravel. Hace un tiempo me pregunté cómo se haría esto en Laravel y encontré un post en Medium dónde lo explicaba de una manera muy sencilla (post en el que me he basado para hacer éste).
El sistema de autenticación sin contraseña funciona de la siguiente manera: simplemente tenemos que indicar nuestro email cuando nos registramos, se nos enviará un correo con un enlace con fecha de caducidad (si caduca, quedará inválido) y cuando entremos en este enlace, nos logueará automáticamente, sin contraseñas.
Nota: yo voy a hacer esta entrada con una instalación limpia de Laravel 5.8
Primero de todo, lo que vamos a hacer va a ser eliminar de las migraciones de la tabla users el campo password:
1 2 |
// eliminar esta línea $table->string('password'); |
Después, también tenemos que eliminar la migración de password_resets porque no la vamos a utilizar.
Una vez tenemos esto, ejecutamos las migraciones:
1 |
php artisan migrate |
Ahora vamos a las vistas tanto del login como del registro y eliminamos los campos de password. Hemos de tener en cuenta que el de registro, también tenemos que eliminar el de confirmación de la contraseña. No los voy a poner poner porque quedaría muy largo el post.
También tenemos que quitar el campo password del modelo de usuario:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
... /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'remember_token', ]; ... |
Vamos a modificar los controladores. El primero que vamos a modificar va a ser el de registro. Los métodos validator y create deberían quedar así:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
... /** * Get a validator for an incoming registration request. * * @param array $data * @return \Illuminate\Contracts\Validation\Validator */ protected function validator(array $data) { return Validator::make($data, [ 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], ]); } /** * Create a new user instance after a valid registration. * * @param array $data * @return \App\User */ protected function create(array $data) { return User::create([ 'name' => $data['name'], 'email' => $data['email'], ]); } ... |
Probamos que el registro funcione correctamente:
Funciona correctamente sin poner ninguna contraseña.
Ahora vamos a modificar el controlador del login. Lo primero que vamos a hacer va a ser sobreescribir dos métodos (validateLogin y login) que quedarán así:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
use Illuminate\Http\Request; use App\User; use Mail; use URL; ... /** * Validate the user login request. * * @param \Illuminate\Http\Request $request * @return void * * @throws \Illuminate\Validation\ValidationException */ protected function validateLogin(Request $request) { $request->validate([ 'email' => 'required|string|exists:users,email', ]); } /** * Handle a login request to the application. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\Http\JsonResponse * * @throws \Illuminate\Validation\ValidationException */ public function login(Request $request) { // validar que el email existe en una base de datos $this->validateLogin($request); // obtener el usuario según el email $user = User::where('email', $request->email)->firstOrFail(); // generar la url temporal $url = URL::temporarySignedRoute( 'login.passwordless', \now()->addMinutes(10), ['user' => $user->id] ); // enviar correo con el link temporal Mail::send('mails.login-passwordless', ['url' => $url], function ($mail) use ($user) { $mail->to($user->email)->subject('Link to login'); }); return \redirect()->route('login.check-email'); } ... |
En el validateLogin lo que estamos haciendo es eliminar de la validación el campo password, ya que lo hemos eliminado de la vista y de la base de datos.
Y en el login lo que estamos haciendo es validar los datos que nos llegan, obtener el usuario a partir del email, generar una url temporal y enviar un email con esta url temporal. Para recibir y ver el email, estoy utilizando Mailtrap.io, si quieres ver como configurarlo echa un vistazo a este post.
Nota: Si la versión de Laravel que tienes es menor a la 5.6.12 no podréis utilizar las url temporales, así que lo tendréis que hacer, por ejemplo, como se hace aquí. Generando una tabla con tokens y comprobar si ese token hace más de X tiempo que se ha generado.
Una vez tenemos esto, vamos a crear la vista del email en views/mails/login-passwordless.blade.php y le ponemos el siguiente contenido:
1 2 3 4 5 6 7 8 |
@component('mail::message') Hello, Click the button below to login. @component('mail::button', ['url' => $url]) Login @endcomponent @endcomponent |
Recuerda publicar las vistas de laravel-mail con php artisan vendor:publish –tag=laravel-mail .
Creamos las rutas necesarias que serán dos: una para el mensaje de información diciendo que se ha enviado un correo a tu email y la otra para la url temporal utilizada anteriormente.
1 2 |
Route::view('login/check-email', 'auth.check-email')->name('login.passwordless'); Route::get('login/{user}', '\App\Http\Controllers\Auth\LoginController@loginPasswordLess')->middleware('signed')->name('login.passwordless'); |
Como ves, la primera ruta que es la de el mensaje de aviso, como no tiene ninguna lógica, lo que se hace es crear una ruta que muestre la vista directamente, para ello utilizamos Route::view. Creamos la vista en views/auth/check-email.blade.php y le ponemos el siguiente código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card"> <div class="card-header">{{ __('Check your email!') }}</div> <div class="card-body"> <p>Check your email. We are going to send a temporal link to login without password.</p> </div> </div> </div> </div> </div> @endsection |
Con todo esto ya debería estar todo funcionando. Vamos a probarlo:
-