banner 2fa

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.

registro de usuario

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.

Google Authenticator

Demo del sistema

Iniciamos sesión en nuestro sistema:


Nos aparece el segundo factor de autenticación:

vista auth.2fa
Abrimos la aplicación Google Authenticator desde el dispositivo móvil:
Google Authenticator desde el movil
Damos clic en COMENZAR y nos aparecerá la siguiente ventana damos clic en escanear código de barras:
agregar una cuenta
Escaneamos el código QR presentado:
escaneo de código
Esto nos generará un código:
Google authenticator con código
Ingresamos el código en nuestra aplicación web:
 ingreso de código
Al dar clic en Enviar y si este código cumple con las indicaciones establecidas, seremos redirigidos a nuestra pantalla principal:

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

Suscríbete a nuestro boletín

Te enviaremos publicaciones con consejos útiles y múltiples recursos para que sigas aprendiendo.

Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.