En algún momento de nuestras vidas cuando estamos creando un sistema se nos ha pasado por la mente que al observar los métodos de autenticación tenemos preguntas como ¿Realmente es el usuario “X” el que está accediendo a mi sistema?, ¿Puedo dar un poco más de seguridad a mi sistema de autenticación?, ¿Cómo protegerme si alguien vio mis credenciales?
Estas preguntas suelen nacer al ver cómo ciertos sitios en especial agencias bancarias nos suelen enviar códigos a nuestros teléfonos móviles para que estos sirvan como un segundo método de verificación al intentar ingresar a un sistema. Para realizar este proceso se utiliza un concepto llamado “Doble factor de Autenticación” o mejor conocido por sus siglas en inglés 2FA – Two Factor Authentication, el cual nos ayuda a protegernos de los denominados ataques de fuerza bruta para el robo de credenciales, ya que al implementar este proceso nos estamos asegurando que cada vez que un usuario envíe una petición de login al sistema éste sea quien en realidad dice ser, este segundo factor puede ser un código de verificación enviado por SMS, e-mail o usando un código QR.
Con Laravel, la ayuda de un componente y la aplicación 2FA de Google podemos hacerlo de una forma super sencilla y en este tutorial te enseñaré cómo hacerlo a través de un código QR.
Objetivo
Para comprender cómo realizar un sistema con doble factor de autenticación realizaremos un mini-sitio mediante el cual cada vez que una persona ingrese el tradicional usuario y contraseña se despliegue una segunda vista con un código QR, el cual será escaneado con nuestro smartphone y al realizar esta acción nos facilitará un código que al ingresarlo en la plataforma recién se dará paso a nuestra pantalla de bienvenida.
Preparación del proyecto
Para comenzar vamos a crear un nuevo proyecto de Laravel con el siguiente comando:
composer create-project laravel/laravel two-factor-authenticator
Debemos crear una base de datos y asociarla a nuestro proyecto a través del archivo de configuración .env
:
DB_CONNECTION=MySQL DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=2fa DB_USERNAME=root DB_PASSWORD=
Debes cambiar los valores de estas variables según las configuraciones del entorno donde esté tu proyecto.
Abrimos el archivo de migración de usuarios y agregamos una columna token_login
a nuestra tabla, la cual tendrá la finalidad de guardar el código generado para la validación de 2 factores quedando el archivo de la siguiente manera:
Schema::create('users', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('email')->unique(); $table->string('password'); $table->string('token_login')->nullable(); $table->rememberToken(); $table->timestamps(); });
Ejecutamos el comando para procesar las migraciones: php artisan migrate
. Para finalizar debemos agregar token_login
al arreglo de la propiedad $fillable
del modelo App\User
.
Para ejecutar este proyecto es necesario iniciar sesión; es por este motivo que usaremos el scaffolding de inicio de sesión que nos provee Laravel a través del comando de artisan: php artisan make:auth
e iniciaremos un servidor para nuestro proyecto con el comando: php artisan serve
.
Creamos un nuevo usuario para probar que la autenticación simple funcione de manera adecuada.
Implementando Doble autenticación
Para que el proyecto soporte doble autenticación nos vamos a redirigir a la página del repositorio central de Composer llamada Packagist.org y buscaremos el componente pragmarx/google2fa-laravel
que nos ayudará a gestionar este proceso y procedemos a instalarlo ejecutando:
composer require pragmarx/google2fa-laravel
Adicionalmente, este componente necesita que instalemos el generador de códigos QR por lo que instalaremos el paquete:
composer require bacon/bacon-qr-code
Como Laravel ya trae por defecto un método que hace posible la autenticación, entonces debemos modificar el controlador LoginController
para que acepte el segundo factor de autenticación.
Esto lo hacemos copiando el método login
del Trait AuthenticatesUsers
en el controlador. En este método añadiremos la llamada a una segunda vista auth.2fa
, donde se implemente la ejecución del componente y modificaremos el valor de la columna token_login
en la tabla users
. El método quedará de la siguiente manera:
use App\User; use Illuminate\Http\Request; use PragmaRX\Google2FA\Google2FA; use App\Http\Controllers\Controller; use Illuminate\Support\Facades\Auth; use BaconQrCode\Renderer\ImageRenderer; use BaconQrCode\Writer as BaconQrCodeWriter; use Illuminate\Foundation\Auth\AuthenticatesUsers; use BaconQrCode\Renderer\Image\ImagickImageBackEnd; use BaconQrCode\Renderer\RendererStyle\RendererStyle; public function login(Request $request) { $this->validateLogin($request); if ($this->hasTooManyLoginAttempts($request)) { $this->fireLockoutEvent($request); return $this->sendLockoutResponse($request); } $user = User::where($this->username(), '=', $request->email)->first(); if (password_verify($request->password, optional($user)->password)) { $this->clearLoginAttempts($request); $user->update(['token_login' => (new Google2FA)->generateSecretKey()]); $urlQR = $this->createUserUrlQR($user); return view("auth.2fa", compact('urlQR', 'user')); } $this->incrementLoginAttempts($request); return $this->sendFailedLoginResponse($request); }
En este método creamos un TOKEN aleatorio de 16 caracteres con Google2FA::generateSecretKey()
y actualizamos el usuario. Este token servirá para generar la URL que dibuje el código QR en la vista auth.2fa
por medio del método createUserUrlQR
que añadimos al controlador LoginController
:
public function createUserUrlQR($user) { $bacon = new BaconQrCodeWriter(new ImageRenderer( new RendererStyle(200), new ImagickImageBackEnd() )); $data = $bacon->writeString( (new Google2FA)->getQRCodeUrl( config('app.name'), $user->email, $user->token_login ), 'utf-8'); return 'data:image/png;base64,' . base64_encode($data); }
La instrucción Google2FA::getQRCodeUrl(config('app.name'), $user->email, $user→token_login)
se alimenta de tres parámetros: el primero es el nombre de la aplicación, segundo el email del usuario con el que estamos iniciando sesión y el tercer parámetro es el token generado.
Es importante recordar que si estamos trabajando en Linux debemos tener la extensión “imagick” por lo que si no la tenemos debemos instalarla de la siguiente manera: apt-get install php7.2-imagick
Creamos la vista auth.2fa
que extenderá de layouts.app
, en ella crearemos una etiqueta tipo imagen para que dibuje el código QR, también crearemos una caja de texto para que el usuario introduzca el código escaneado a través de la app de Google Authenticator.
@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">{{ __('Login 2FA') }}</div> <div class="card-body"> <form method="POST" action="{{ route('login.2fa',$user->id) }}" aria-label="{{ __('Login') }}"> @csrf <div class="form-group row"> <div class="col-lg-4"> <img id="imgQR" src="{{$urlQR}}"/> </div> <div class="col-lg-8"> <div class="form-group"> <label for="code_verification" class="col-form-label"> {{ __('CÓDIGO DE VERIFICACIÓN') }} </label> <input id="code_verification" type="text" class="form-control{{ $errors->has('code_verification') ? ' is-invalid' : '' }}" name="code_verification" value="{{ old('code_verification') }}" required autofocus> @if ($errors->has('code_verification')) <span class="invalid-feedback" role="alert"> <strong>{{ $errors->first('code_verification') }}</strong> </span> @endif </div> <button type="submit" class="btn btn-primary">ENVIAR</button> </div> </div> </form> </div> </div> </div> </div> </div> @endsection
Como podemos observar necesitamos declarar una ruta que gestione el proceso de validación del código escaneado, por lo tanto dentro del archivo routes/web.php
agregamos lo siguiente:
Route::post('/login-two-factor/{user}', 'Auth\LoginController@login2FA')->name('login.2fa');
Dentro del controlador LoginController
procedemos a crear el método login2FA
que se encargará de validar el código generado desde el smartphone y comprobarlo contra el token almacenado en la tabla de usuarios:
public function login2FA(Request $request, User $user) { $request->validate(['code_verification' => 'required']); if ((new Google2FA())->verifyKey($user->token_login, $request->code_verification)) { $request->session()->regenerate(); Auth::login($user); return redirect()->intended($this->redirectPath()); } return redirect()->back()->withErrors(['error'=> 'Código de verificación incorrecto']); }
Gracias al model binding de Laravel el objeto $user
ya viene cargado desde la base de datos por lo que procedemos a evaluar que el código generado desde el smartphone pertenezca al token almacenado en la tabla usuarios y a su vez que sea válido para poder iniciar sesión.
Como dato final en nuestro sistema colocaremos en layouts/app.blade.php
las líneas de código necesarias para presentar los errores en laravel quedando de la siguiente manera:
<main class="py-4"> @if ($errors->any()) <div class="alert alert-danger"> <ul> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif @yield('content') </main>
Descargando la Aplicación
Google Authenticator es una aplicación gratuita que podemos descargar de la tienda de GooglePlay o de la tienda de AppStore. Cuando instalamos la aplicación en nuestro smartphone nos permitirá leer el código QR generado y nos presentará en pantalla unos dígitos, los cuales serán ingresados en nuestra aplicación para poder iniciar sesión. Cabe indicar que estos dígitos cambian automáticamente cada 30 segundos, pero no te preocupes como son códigos de desbloqueo válidos para el token almacenado te permitirán ingresar sin ningún problema.
Demo del sistema
Iniciamos sesión en nuestro sistema:
Nos aparece el segundo factor de autenticación:
Abrimos la aplicación Google Authenticator desde el dispositivo móvil:
Damos clic en COMENZAR y nos aparecerá la siguiente ventana damos clic en escanear código de barras:
Escaneamos el código QR presentado:
Esto nos generará un código:
Ingresamos el código en nuestra aplicación web:
Al dar clic en Enviar y si este código cumple con las indicaciones establecidas, seremos redirigidos a nuestra pantalla principal:
Sin embargo, cuando ingresas un código de verificación erróneo te saldrá el error «Código de verificación incorrecto» y no permitirá iniciar sesión.
Material relacionado
- Métodos de Autenticación con Laravel
- Autenticación basada en tokens con Laravel 5.3 y TDD
- Autenticación con GitHub usando Laravel Socialite
Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.