
Compartir:
Michael es un ingeniero de software políglota, empeñado en reducir la complejidad de los sistemas y hacerlos más predecibles. Trabaja con una gran variedad de lenguajes y herramientas, y comparte sus conocimientos técnicos con audiencias de todo el mundo en grupos de usuarios y conferencias. En el día a día, Michael es un antiguo defensor de los desarrolladores en Vonage, donde pasaba su tiempo aprendiendo, enseñando y escribiendo sobre todo tipo de tecnología.
Login 2FA con Laravel y Nexmo
Este artículo apareció originalmente en michaelheap.com antes de Michael se uniera a Nexmo equipo¡!
Hace poco escribí sobre bootstrapping Laravel con autenticación de usuario y lo fácil que es (en serio, se tarda menos de 5 minutos). Eso nos proporciona un gran punto de partida para nuestras aplicaciones, pero luego me topé con este post sobre la integración de la autenticación de dos factores con Google Authenticator y empecé a pensar en Nexmo Verify.
Recientemente integré Verify en un chatbot sin ningún problema, y pensé que podría ser algo útil para integrar en mi flujo de inicio de sesión Laravel.
Vonage API Account
To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.
This tutorial also uses a virtual phone number. To purchase one, go to Numbers > Buy Numbers and search for one that meets your needs.
Recoger el número de teléfono del usuario
Necesitamos el número de teléfono del usuario, ya que sin él no podemos enviarle un mensaje de verificación. Podríamos recopilarlo después de que el usuario se haya registrado, pero he decidido hacerlo en el momento del registro.
Lo primero que tenemos que hacer es modificar la tabla de usuarios para que haya un campo preparado para almacenar el número de teléfono del usuario. Para ello, vamos a crear una nueva migración para modificar nuestra tabla users tabla:
Esto crea un archivo en la carpeta database/migrations llamado <current time>_add_users_phone_number.php. Abra ese archivo y sustituya su contenido por lo siguiente:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddUsersPhoneNumber extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('phone_number');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('phone_number');
});
}
}Esta migración añade una columna llamada phone_number cuando se ejecuta, y elimina la columna cuando se revierte. Aplíquela ahora ejecutando php artisan migrate en su terminal.
A continuación, tenemos que añadir una entrada de texto a nuestro formulario de registro para que el usuario proporcione su número de teléfono. Edite resources/views/auth/register.blade.php y añade lo siguiente al final del formulario, justo antes del botón de envío:
<div class="form-group{{ $errors->has('phone_number') ? ' has-error' : '' }}">
<label for="name" class="col-md-4 control-label">Phone Number</label>
<div class="col-md-6">
<input id="name" type="tel" class="form-control" name="phone_number" value="{{ old('phone_number') }}" required autofocus>
@if ($errors->has('phone_number'))
<span class="help-block">
<strong>{{ $errors->first('phone_number') }}</strong>
</span>
@endif
</div>
</div>Si ahora visitamos http://localhost:8000/register, deberíamos ver el campo de número de teléfono en la parte inferior de nuestro formulario de registro. Ya casi lo tenemos, pero aún falta una parte clave: no guardamos el número que el usuario proporciona en nuestro nuevo campo de la base de datos.
Laravel mantiene toda su lógica para registrar un usuario en el archivo app/Http/Controllers/Auth/RegisterController.php archivo. Ábrelo y echa un vistazo - deberías ver un método validator y un método create método. Necesitaremos cambiar ambos para guardar el número de teléfono de nuestro usuario.
Empecemos con el método validator método. Tenemos que añadir una nueva entrada para phone_number para asegurarnos de que el número proporcionado es válido. He elegido ser bastante estricto con mis reglas de validación, requiriendo que tenga exactamente 12 caracteres y que sea único para todos los usuarios - puedes elegir ser menos estricto. Después de añadir una regla de validación, su método validator debería ser similar al siguiente:
return Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|min:6|confirmed',
'phone_number' =>; 'required|size:12|unique:users',
]);Una vez que los datos han superado las reglas de validación que hemos especificado, tenemos que almacenarlos en la base de datos. Para ello, editamos el método create y añadimos una línea que guarde nuestro número de teléfono. Todos los datos de la solicitud entrante están disponibles en la variable $data por lo que es tan sencillo como añadir una sola línea:
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
'phone_number' =>; $data['phone_number']
]);
Si intentamos añadir un usuario ahora, no funcionará como se espera. Esto se debe a una característica de seguridad de Laravel que impide la asignación masiva de propiedades a una clase. No hemos informado a nuestra clase User que phone_number es un campo válido, por lo que rechazará nuestra petición de guardarlo. Para solucionar este problema, edita app/User.php y añade phone_number al $fillable array:
protected $fillable = [
'name', 'email', 'password', 'phone_number'
];Después de hacer este cambio, no dude en registrar una Account a través de la página de registro y acceder a nuestra aplicación.
Añadir Nexmo Verify
Ahora que tenemos el número de teléfono del usuario, estamos en posición de empezar a implementar nuestra lógica Verify. Laravel ejecuta la solicitud de inicio de sesión del usuario a través de app/Http/Controllers/Auth/LoginController.php para averiguar si las credenciales proporcionadas son válidas o no. Si las credenciales son válidas, Laravel buscará un método authenticated en el directorio LoginController. Si el método existe se ejecutará la lógica allí. Aquí es donde añadiremos nuestra lógica de autenticación de dos factores.
Abre app/Http/Controllers/Auth/LoginController.php y añade lo siguiente en la parte superior junto a las otras use declaraciones:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Contracts\Auth\Authenticatable;Necesitamos estas tres use para poder sugerir nuestro método authenticated que añadiremos a continuación. Añade lo siguiente a la clase LoginController clase:
public function authenticated(Request $request, Authenticatable $user)
{
Auth::logout();
$request->session()->put('verify:user:id', $user->id);
// @TODO: Send the Verify SMS here
return redirect('verify');
}
Este código cerrará la sesión del usuario de nuevo, almacenando su ID de usuario en la sesión para que sepamos con qué usuario intentó entrar. Una vez que se haya completado la solicitud de verificación, utilizaremos este ID para volver a iniciar sesión automáticamente.
Activación de una solicitud de Verify
Te habrás dado cuenta de que hay un @TODO para añadir la lógica de Verify SMS. Actualmente no tenemos una manera de enviar un SMS a través de Nexmo todavía, así que vamos a cuidar de que a continuación. Afortunadamente, Nexmo tiene un paquete Laravel que hace esto agradable y fácil para nosotros. Siguiendo el README de ese proyecto, instalamos tanto el cliente Nexmo como el proveedor de servicios Laravel con Composer:
Después de instalarlo, necesitamos decirle a Laravel que nuestro cliente existe. Necesitamos editar dos secciones en config/app.php para hacer esto - providers y aliases.
Añada lo siguiente a providers:
Nexmo\Laravel\NexmoServiceProvider::classAñada lo siguiente a aliases:
'Nexmo' => \Nexmo\Laravel\Facade\Nexmo::class
Por último, tenemos que ejecutar php artisan vendor:publish para generar nuestro archivo de configuración de Nexmo. Una vez que hayamos ejecutado este comando, podemos editar config/nexmo.php y proporcionar nuestras credenciales API en api_key y api_secret. Podemos proporcionarlas directamente aquí, o podemos usar el comando .env similar al archivo de configuración de la base de datos. Voy a utilizar el archivo .env así que he cambiado config/nexmo.php para que contenga lo siguiente:
'api_key' => env('NEXMO_KEY', ''),
'api_secret' => env('NEXMO_SECRET', ''),
A continuación, en .envhe añadido dos entradas en la parte inferior del archivo - NEXMO_KEY y NEXMO_SECRET:
Ahora que el cliente Nexmo está configurado, podemos volver a app/Http/Controllers/Auth/LoginController.php e implementar nuestro sistema de notificaciones. Reemplaza el @TODO que dejamos con lo siguiente:
$verification = Nexmo::verify()->start([
'number' => $user->phone_number,
'brand' => 'Laravel Demo'
]);
$request->session()->put('verify:request_id', $verification->getRequestId());
Esto activará una solicitud de verificación a través de Nexmo al número de teléfono que tenemos registrado para ese usuario. También tendremos que añadir use Nexmo; al principio del archivo para que nuestra fachada esté disponible. Una vez hecho esto, podrá iniciar sesión y activar una solicitud de verificación, ¡pero no lo haga todavía! No tenemos una manera para que el usuario proporcione su código de verificación, por lo que no será capaz de confirmar su identidad.
Verificación de la solicitud
Al final de LoginController::authenticated redirigimos al usuario a una /verify url. Es hora de registrar esa ruta con Laravel y escribir una implementación para ella.
Abra routes/web.php y añada lo siguiente al final del mismo:
Route::get('/verify', 'VerifyController@show')->name('verify');
Route::post('/verify', 'VerifyController@verify')->name('verify');
Esto registra dos rutas (a GET y a POST en /verify) que usaremos para verificar el código de un usuario. Le hemos dicho a Laravel que debe llamar a las rutas show y verify en el método VerifyController para estas peticiones, por lo que debemos generar el controlador usando artisan:
php artisan make:controller VerifyControllerEsto creará un archivo en app/Http/Controllers/VerifyController.php - deberá sustituir su contenido por el siguiente
<?php
namespace App\Http\Controllers;
use Auth;
use Nexmo;
use Illuminate\Http\Request;
class VerifyController extends Controller
{
public function show(Request $request) {
return view('verify');
}
public function verify(Request $request) {
return 'Not Implemented';
}
}Esto es suficiente para mostrar la vista Verify cuando alguien hace una petición GET a /verify. Una vez más, este archivo no existe todavía, así que vamos a crearlo en resources/views/verify.blade.php con el siguiente contenido:
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Verify</div>
<div class="panel-body">
<form class="form-horizontal" role="form" method="POST" action="{{ route('verify') }}">
{{ csrf_field() }}
<div class="form-group{{ $errors->has('code') ? ' has-error' : '' }}">
<label for="code" class="col-md-4 control-label">Code</label>
<div class="col-md-6">
<input id="code" type="number" class="form-control" name="code" value="{{ old('code') }}" required autofocus>
@if ($errors->has('code'))
<span class="help-block">
<strong>{{ $errors->first('code') }}</strong>
</span>
@endif
</div>
</div>
<div class="form-group">
<div class="col-md-6 col-md-offset-4">
<button type="submit" class="btn btn-primary">
Verify Account
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsectionHay mucho HTML, pero lo único que hace es mostrar un formulario con un botón de envío. Puede visitar la página página Verify para verlo ahora.
Ahora que tenemos nuestra página para introducir nuestro código Verify, todo lo que queda por hacer es verificar el código proporcionado con Nexmo. Reemplace su método verify en VerifyController por el siguiente código. Este método valida que nuestros datos de entrada es de 4 caracteres de longitud (Nexmo códigos de verificación puede ser de 4 o 6 caracteres de longitud, estoy trabajando con 4) a continuación, comprueba el código proporcionado con Nexmo. Si no se valida, se lanza una excepción y devolvemos un error al usuario. De lo contrario, obtenemos el ID de usuario de la sesión, iniciamos la sesión del usuario y redirigimos al controlador de inicio.
public function verify(Request $request) {
$this->validate($request, [
'code' => 'size:4',
]);
try {
Nexmo::verify()->check(
$request->session()->get('verify:request_id'),
$request->code
);
Auth::loginUsingId($request->session()->pull('verify:user:id'));
return redirect('/home');
} catch (Nexmo\Client\Exception\Request $e) {
return redirect()->back()->withErrors([
'code' => $e->getMessage()
]);
}
}En este punto, nuestra integración debería estar funcionando de extremo a extremo. Si guarda todos los cambios e intenta iniciar sesión, debería ser redirigido a la página verify y recibirás un mensaje de texto con tu código de verificación. Introduce el código y se iniciará la sesión.
¡Enhorabuena! Acaba de integrar la autenticación de dos factores con Nexmo Verify en su aplicación Laravel.
Arreglar las asperezas
Aunque funciona, aún quedan algunas asperezas por resolver. Por ejemplo, un usuario puede conectarse por segunda vez sin confirmar la primera solicitud de Verify. También puede acceder a la página /verify Verify activa. Por último, no verificamos su identidad después del registro, sino sólo cuando se desconectan y vuelven a intentar conectarse.
No vamos a resolver estas cuestiones en este post, ¡te las dejo como ejercicio!
Compartir:
Michael es un ingeniero de software políglota, empeñado en reducir la complejidad de los sistemas y hacerlos más predecibles. Trabaja con una gran variedad de lenguajes y herramientas, y comparte sus conocimientos técnicos con audiencias de todo el mundo en grupos de usuarios y conferencias. En el día a día, Michael es un antiguo defensor de los desarrolladores en Vonage, donde pasaba su tiempo aprendiendo, enseñando y escribiendo sobre todo tipo de tecnología.
