Cómo implementar la verificación de la cuenta e iniciar sesión por teléfono en Laravel

April 12, 2019
Redactado por
Victor Abbah
Colaborador
Las opiniones expresadas por los colaboradores de Twilio son propias.

Cómo implementar la verificación de la cuenta e iniciar sesión por teléfono en Laravel

Es posible que en ocasiones desee crear una app que utilice un número de teléfono junto con una contraseña como medio de autenticación en lugar de un correo electrónico con una contraseña, como se usa habitualmente. En otros casos, no usa necesariamente un número de teléfono como un como medio de autenticación, pero tener un número de teléfono es fundamental para su app. En tales situaciones, es muy importante que verifique que los números de teléfono que proporcionan los usuarios sean válidos y estén en funcionamiento. Una manera de hacer esto es mediante una llamada en la que se brindará un código que tendrán que proporcionar a la app. Si usted usa Gmail, probablemente ya esté familiarizado con la verificación por llamada de voz. En este artículo, le mostraremos cómo puede lograr eso utilizando Laravel y el excelente servicio de Twilio. Vamos por ello.

Requisitos técnicos

En este tutorial, supondremos que:

Crear y configurar una nueva app Laravel

Vamos a crear una nueva instalación de Laravel en nuestro directorio “estacionado” por el valet. Lo hacemos ejecutando el siguiente comando:

$ laravel new twilio

Esto supone que ya hemos instalado el instalador de Laravel. Si no lo tiene, puede verificar Documentación oficial de Laravel para obtener instrucciones relacionadas. Como alternativa, puede instalar una nueva app Laravel usando Composer. Ejecute el siguiente comando en la ventana de terminal:

$ composer create-project --prefer-dist laravel/laravel twilio

A continuación, debemos crear un archivo .env en el que podamos almacenar todas las configuraciones que nuestra app necesita. Ejecute el siguiente comando en su terminal:

$ cp .env.example .env

Este comando crea el .env utilizando .env.example como plantilla. Abra el archivo .env y agregue las credenciales de database nameusername y password.

Cree una base de datos y establezca las credenciales de esa base de datos en el archivo .env recién creado.

credenciales de esa base de datos en el archivo .env

Si completó todos estos pasos correctamente, debería ver su nueva app en el navegador al ejecutar valet link desde su terminal. En este caso, nombré la carpeta “twilio” y especificé una extensión .devs, por lo que mi dirección URL es twilio.devs.

Pantalla de Laravel

Configurar las rutas y vista de autenticación

Después de configurar las credenciales de la base de datos, ahora podemos configurar la autenticación mediante el comando Artisan. Laravel ofrece un andamiaje rápido de todas las rutas y vistas que se necesitan para autenticar mediante este simple comando. Con este comando único, podemos instalar una vista para el diseño, el registro y el inicio de sesión, así como rutas para todos los puntos finales de autenticación.

$ php artisan make:auth

Después de ejecutar este comando, ejecute la migración.

$ php artisan migrate

Y cuando revise su base de datos, debe ver las tablas creadas con el comando artisan make:auth. Si actualiza la app, también verá que ahora algunos enlaces de autenticación se agregan automáticamente a la interfaz.

Pantalla de Laravel

Configurar Twilio

Lo siguiente que haremos es agregar nuestras credenciales de Twilio al archivo .env. Visite el panel de control de Twilio y copie su SID de la cuenta y su token de autenticación.

TWILIO_SID=<twilio_sid>
TWILIO_AUTH_TOKEN=<twilio_auth_token>

SID de cuenta y token de la consola de Twilio

Instalar el SDK de Twilio a través de Composer

A continuación, instalaremos la Biblioteca auxiliar de Twilio PHP. Esto nos ayudará a hablar con la API de Twilio desde nuestro código. Ejecute el siguiente comando en su terminal:

$ composer require twilio/sdk

Unir todo

Este es el punto en el que comenzamos a hacer el trabajo real de asegurarnos de que nuestra app llame al usuario al momento del registro y que el usuario también pueda iniciar sesión con su número de teléfono. No queremos que nuestros usuarios tengan problemas para iniciar sesión después del registro.

Modificar nuestro modelo de usuario

Ahora que tenemos todo configurado, necesitamos empezar a hacer ajustes en nuestra app para que durante el registro acepte un número de teléfono en lugar de un correo electrónico. En primer lugar, cambiaremos el archivo de migración de tabla de los usuarios generado para nosotros cuando ejecutamos el comando php artisan make:auth de esta forma:

        public function up()
        {
            Schema::create('users', function (Blueprint $table) {
                $table->increments('id');
                $table->string('name');
                $table->string('phone')->unique();
                $table->string('verification_code')->unique()->nullable();
                $table->timestamp('phone_verified_at')->nullable();
                $table->string('password');
                $table->rememberToken();
                $table->timestamps();
            });
        }

El código anterior cambia el campo de correo electrónico por el teléfono. También agrega a la tabla del usuario un campo para el verification_code que almacena el código que se le indicará a los usuarios cuando se los llame. Se agrega también phone_verified_at como campo a la tabla del usuario para guardar el momento en que se verifica el número de teléfono del usuario.

Además, debemos hacer algunos cambios en el modelo app/User.php. El siguiente código garantiza que los campos de teléfono, nombre y contraseña se puedan asignar de forma masiva. Puede leer más acerca de las asignaciones masivas en Documentación de Laravel:

        protected $fillable = [
            'name', 'phone', 'password',
        ];

        protected $casts = [
            'phone_verified_at' => 'datetime',
        ];

Sin embargo, en el archivo app/User.php, agregaremos un par de métodos que ayudarán en el proceso de verificación. Agregue los siguientes métodos:

        public function hasVerifiedPhone()
        {
            return ! is_null($this->phone_verified_at);
        }

        public function markPhoneAsVerified()
        {
            return $this->forceFill([
                'phone_verified_at' => $this->freshTimestamp(),
            ])->save();
        }

El método hasVerifedPhone simplemente comprueba si el número de teléfono del usuario está verificado. markPhoneAsVerified verifica el número de teléfono del usuario mediante la configuración del campo phone_verified_at del usuario en la marca de tiempo actual.

Modificar nuestros controladores de autenticación

Como mencioné anteriormente, no queremos una situación en la que un usuario se registre pero tenga problemas para iniciar sesión simplemente porque nuestra app espera un correo electrónico para la autenticación. Asegurémonos de que eso no ocurra. Abra app/Http/Controllers/Auth/LoginController.php y agregue el siguiente método:

        public function username()
        {
            return 'phone';
        }

Este método devuelve el nombre de usuario de inicio de sesión que utilizará el controlador. Básicamente, indica: “Este es el campo de la base de datos que debe buscar como nombre de usuario cuando autentica un usuario”.

A continuación, editamos app/Http/Controllers/Auth/RegisterController.php y cambiamos los métodos validator, y create de esta manera:

        protected function validator(array $data)
        {
            return Validator::make($data, [
                'name' => ['required', 'string', 'max:255'],
                'phone' => ['required', 'string', 'unique:users'],
                'password' => ['required', 'string', 'min:6', 'confirmed'],
            ]);
        }

        protected function create(array $data)
        {
            return User::create([
                'name' => $data['name'],
                'phone' => $data['phone'],
                'password' => Hash::make($data['password']),
            ]);
        }

Los métodos anteriores solo nos permiten validar y almacenar los datos ingresados correctamente por el usuario durante el registro. Nada sofisticado.

Modificar nuestras vistas de autenticación

Ahora tenemos que empezar a trabajar con lo que nuestros usuarios realmente interactuarán: las vistas de autenticación. Tenemos que editar las vistas de registro e inicio de sesión para esperar los números de teléfono en lugar de los correos electrónicos. Abra resources/views/auth/register.blade.php y modifique la parte que pide el correo electrónico con lo siguiente:

        <div class="form-group row">
            <label for="phone" class="col-md-4 col-form-label text-md-right">Phone</label>

            <div class="col-md-6">
                <input id="phone" type="text" class="form-control{{ $errors->has('phone') ? ' is-invalid' : '' }}" name="phone" value="{{ old('phone') }}" required>

                @if ($errors->has('phone'))
                    <span class="invalid-feedback" role="alert">
                        <strong>{{ $errors->first('phone') }}</strong>
                    </span>
                @endif
            </div>
        </div>

Además, abra resources/views/auth/login.blade.php y modifique la parte que pide el correo electrónico por lo siguiente:

        <div class="form-group row">
            <label for="phone" class="col-md-4 col-form-label text-md-right">Phone</label>

            <div class="col-md-6">
                <input id="phone" type="text" class="form-control{{ $errors->has('phone') ? ' is-invalid' : '' }}" name="phone" value="{{ old('phone') }}" required autofocus>

                @if ($errors->has('phone'))
                    <span class="invalid-feedback" role="alert">
                        <strong>{{ $errors->first('phone') }}</strong>
                    </span>
                @endif
            </div>
        </div>

Hemos hecho ajustes correctamente para que nuestra app acepte números de teléfono durante la autenticación. ¡Bien hecho!

Middleware, controlador y ruta de verificación

En este punto, necesitamos proteger nuestras rutas de los usuarios con cuentas no verificadas. Lo hacemos mediante la creación de un middleware que se aplique a las rutas que necesitamos proteger. También debemos crear el controlador y las rutas necesarias para la verificación. Vamos por ello.

Crear un middleware

Laravel facilita la creación de un middleware. Simplemente cámbiese a su terminal y ejecute el comando artisan:

$ php artisan make:middleware EnsurePhoneIsVerified

Abra el middleware y actualice el siguiente código al método handle:

        if (! $request->user()->hasVerifiedPhone()) {
            return redirect()->route('phoneverification.notice');
        }

        return $next($request);

Ahora tenemos que registrar el middleware para que la aplicación lo sepa. Abra app/Http/Kernel.php y agregue esto a la matriz $routeMiddleware.

        'verifiedphone' => \App\Http\Middleware\EnsurePhoneIsVerified::class,

Aplique el middleware a la ruta principal. Abra routes/web.php y edítelo de la siguiente manera:

Route::get('/home', 'HomeController@index')->name('home')->middleware('verifiedphone'); 

Ahora, cualquier usuario que visite la ruta local sin verificar su número de teléfono será redirigido a la ruta que especificamos en el middleware.

Agregar las rutas necesarias

Abra rutas/web.php y agregue el siguiente código:

        Route::get('phone/verify', 'PhoneVerificationController@show')->name('phoneverification.notice');
        Route::post('phone/verify', 'PhoneVerificationController@verify')->name('phoneverification.verify');

Como puede ver en las rutas anteriores, estamos dirigiendo el tráfico a un PhoneVerificationController que aún no tenemos. Creemos eso rápidamente mediante artisan. Ejecute el siguiente comando:

$ php artisan make:controller PhoneVerificationController

Abra ese controlador y agregue el siguiente código:

<?php
        public function show(Request $request)
    {
        return $request->user()->hasVerifiedPhone()
                        ? redirect()->route('home')
                        : view('verifyphone');
    }

    public function verify(Request $request)
    {
        if ($request->user()->verification_code !== $request->code) {
            throw ValidationException::withMessages([
                'code' => ['The code your provided is wrong. Please try again or request another call.'],
            ]);
        }

        if ($request->user()->hasVerifiedPhone()) {
            return redirect()->route('home');
        }

        $request->user()->markPhoneAsVerified();

        return redirect()->route('home')->with('status', 'Your phone was successfully verified!');
    }

En el método show, estamos diciendo: “si el usuario tiene un teléfono verificado, simplemente redirija al usuario a la ruta local”; de lo contrario, vuelva a una vista que le notifique que debe proporcionar un código de verificación. En el método de verificación, estamos verificando a un usuario. Observe cómo utilizamos el método hasVeriedPhone que creamos antes en app/User.php. Ahora, creemos una vista en la que los usuarios puedan proporcionar su código de verificación.

Cree un archivo en resources/views/verifyphone.blade.php y agréguele el siguiente código:

        @extends('layouts.app')
        @section('content')
        <div class="container">
            <div class="row justify-content-center">
                <div class="col-md-8">
                    @if (session('status'))
                        <div class="alert alert-success" role="alert">
                            {{ session('status') }}
                        </div>
                    @endif
                    <div class="card">
                        <div class="card-header">Verify your phone</div>
                        <div class="card-body">
                            <p>Thanks for registering with our platform. We will call you to verify your phone number in a jiffy. Provide the code below.</p>

                            <div class="d-flex justify-content-center">
                                <div class="col-8">
                                    <form method="post" action="{{ route('phoneverification.verify') }}">
                                        @csrf
                                        <div class="form-group">
                                            <label for="code">Verification Code</label>
                                            <input id="code" class="form-control{{ $errors->has('code') ? ' is-invalid' : '' }}" name="code" type="text" placeholder="Verification Code" required autofocus>
                                            @if ($errors->has('code'))
                                                <span class="invalid-feedback" role="alert">
                                                    <strong>{{ $errors->first('code') }}</strong>
                                                </span>
                                            @endif
                                        </div>
                                        <div class="form-group">
                                            <button class="btn btn-primary">Verify Phone</button>
                                        </div>
                                    </form>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        @endsection

Llamar al usuario

Tenemos todo configurado. Ahora tenemos que ingresar al flujo de registro y llamar al usuario. Laravel proporciona un buen método para que lo hagamos. En app/Http/Controllers/Auth/RegisterController.php, después de un registro correcto, Laravel llama a un método registered. Esto significa que podemos anular este método para llamar al usuario después de un registro correcto. Abra app/Http/Controllers/Auth/RegisterController.php y agregue el método:

        protected function registered(Request $request, User $user)
        {
            $user->callToVerify();
            return redirect($this->redirectPath());
        }

En este método, llamamos al método callToVerify para el usuario que aún no hemos definido. Después de eso, redirigimos al usuario a la ruta de inicio. Definamos rápidamente ese método. Abra app/User.php y agregue el siguiente código:

<?php
        use Twilio\Rest\Client;
.
.

        public function callToVerify()
        {
            $code = random_int(100000, 999999);
            
            $this->forceFill([
                'verification_code' => $code
            ])->save();

            $client = new Client(env('TWILIO_SID'), env('TWILIO_AUTH_TOKEN'));

            $client->calls->create(
                $this->phone,
                "+15306658566", // REPLACE WITH YOUR TWILIO NUMBER
                ["url" => "http://your-ngrok-url>/build-twiml/{$code}"]
            );
        }

Aquí es donde en realidad llamamos al usuario y le informamos el código de verificación. Primero, generamos un código aleatorio de 6 dígitos y se lo almacenamos al usuario. Después de eso, creamos un cliente que nos ayude a hablar con la API de Twilio utilizando las credenciales que almacenamos en .env anteriormente.

En este punto, es de suma importancia comprender cómo funciona API de voz programable de Twilio. El método create acepta 3 parámetros:

  • El número de teléfono al que se llamará. Para nosotros, este será el número de teléfono del usuario. Si está en modo de prueba, este debe ser un número de teléfono verificado.
  • El segundo parámetro es el número de teléfono que realiza la llamada. Debe ser un número de teléfono Twilio habilitado para usar con voz creado en su consola. Si actualmente no posee un número de teléfono Twilio con la funcionalidad Voice, deberá comprar o crear uno para la prueba.
  • El tercer parámetro es, para mí, la parte más interesante de la API de voz programable de Twilio. El tercer parámetro es una dirección URL a un archivo XML que debe devolver una TwiML (Twilio Markup Language) válida o una dirección URL a su aplicación, a la que Twilio realiza una solicitud HTTP para solicitar instrucciones sobre cómo manejar la llamada. Debe ser una dirección URL activa, ya que Twilio le enviará de vuelta una solicitud HTTP. Aquí es donde entra en juego Ngrok. Nos ayudará a obtener una URL activa para nuestra aplicación.

Definamos la ruta y un método en PhoneVerificationController.php. Abra routes/web.php y edítela de la siguiente manera:

Route::post('build-twiml/{code}', 'PhoneVerificationController@buildTwiMl')->name('phoneverification.build'); 

Abra PhoneVerificationController.php y edítela de la siguiente manera:

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Twilio\TwiML\VoiceResponse;
.
.

        public function buildTwiML($code)
        {
            $response = new VoiceResponse();
            $response->say("Hi, thanks for Joining. This is your verification code. {$code}. I repeat, {$code}.");
            echo $response;
        }

Una cosa importante que debemos hacer a continuación es desactivar las verificaciones de CSRF en la ruta a la que Twilio realizará una solicitud HTTP. Esto es muy importante. Si no las desactivamos, Laravel no permitirá el acceso y Twilio no podrá saber cómo manejar la llamada. Para ello, editaremos app/Http/Middleware/VerifyCsrfToken.php como se indica a continuación:

        protected $except = [
            '/build-twiml/*'
        ];

En este punto, cuando registre un usuario, recibirá una llamada de Twilio. Todo funciona bien, pero noté algo que necesitamos arreglar. La voz programable de Twilio intenta leer el código como una palabra en lugar de deletrear cada letra. Por ejemplo, si el código es qwerty, Twilio tratará de pronunciarlo como una palabra, como es de esperar. Pero eso no es lo que necesitamos. Necesitamos que diga cada letra por separado. Eso significa que tenemos que ponerlo como q.w.r.t.y, y de esta manera, el código no se leerá como una palabra. Edite PhoneVerificationController.php nuevamente de esta forma:

        public function buildTwiMl($code)
        {
            $code = $this->formatCode($code);
            $response = new VoiceResponse();
            $response->say("Hi, thanks for Joining. This is your verification code. {$code}. I repeat, {$code}.");
            echo $response;
        }

        public function formatCode($code)
        {
            $collection = collect(str_split($code));
            return $collection->reduce(
                function ($carry, $item) {
                    return "{$carry}. {$item}";
                }
            );
        }

Ejecutar la migración

Debido a que actualizamos la tabla de migración del usuario, tenemos que actualizar las columnas para incluir los cambios que realizamos. Ejecute el siguiente comando para actualizar las tablas:

$ php artisan migrate:refresh

Y eso es todo.

Pruebas

Para probar, haga lo siguiente:

  • Ejecute valet share y copie la URL de ngrok.
  • En app/User.php, en el método callToVerify(), reemplace “your-ngrok-url” con la URL que copió arriba.
  • Visite su sitio web y regístrese con un número de teléfono.
  • Ingrese el código que escuchó durante la llamada en la vista a la que se le dirige después del registro.

NOTA: Durante el registro, los números de teléfono deben tener el formato “+” y el código del país; por ejemplo, +16175551212 (formato E.164).

Conclusión

Aunque no lo crea, esto es todo lo que se necesita para realizar la verificación telefónica mediante voz programable de Twilio, Twilio TwiML™ y Laravel. Excelente.

Este artículo fue traducido del original "How to Implement Account Verification and Login by Phone in Laravel". Mientras estamos en nuestros procesos de traducción, nos encantaría recibir sus comentarios en help@twilio.com - las contribuciones valiosas pueden generar regalos de Twilio.