Como verificar números de telefone em um aplicativo Laravel PHP com o Twilio Verify

November 19, 2019
Escrito por
Brian Iyoha
Contribuidor
As opiniões expressas pelos colaboradores da Twilio são de sua autoria

Como verificar números de telefone em um aplicativo Laravel PHP com o Twilio Verify

Neste tutorial, veremos como verificar números de telefone usando o Twilio Verify criando um sistema de autenticação simples em Laravel.

Twilio Verify facilita e torna mais seguro verificar o número de telefone de um usuário em comparação aos sistemas de verificação personalizados. Ele garante que o número de telefone seja válido enviando um código curto via SMS para o número durante o registro. Isso pode ajudar a reduzir o número de contas falsas criadas e as taxas de falha ao enviar notificações por SMS aos usuários.

Pré-requisito

Para seguir este tutorial, você precisará do seguinte:

Primeiros passos

Crie um novo projeto Laravel usando Instalador do Laravel. Se não o instalou ou se preferir usar o Composer, você pode verificar como fazer isso na Documentação do Laravel. Execute este comando na janela do console para gerar um novo projeto Laravel:

$ laravel new twilio-phone-verify  

Agora, mude o diretório de trabalho para twilio-phone-verify e instale o SDK da Twilio PHP via composer:

$ cd twilio-phone-verify
$ composer require twilio/sdk

Se o Composer não estiver instalado no computador, siga as instruções disponíveis aqui para instalá-lo.

Você precisará de suas credenciais da Twilio no dashboard da Twilio para concluir a próxima etapa. Acesse o dashboard e anote o account_sid (SID da conta) e o auth_token (token de autenticação).

Painel do console da Twilio

Navegue até a seção Verify (Verificar) para criar um novo Serviço Twilio Verify. Anote o sid gerado para você após a criação do serviço Verify, pois ele será usado para autenticar a instância do SDK do Verify.

Serviços do Twilio Verify

Atualize o arquivo .env com suas credenciais da Twilio. Abra o.env localizado na raiz do diretório do projeto e adicione estes valores:

TWILIO_SID="INSERT YOUR TWILIO SID HERE"
TWILIO_AUTH_TOKEN="INSERT YOUR TWILIO TOKEN HERE"
TWILIO_VERIFY_SID="INSERT YOUR TWILIO SYNC SERVICE SID"

Como configurar o banco de dados

Este tutorial exigirá um banco de dados MySQL para seu aplicativo. Se você usa um cliente MySQL como phpMyAdmin para gerenciar seus bancos de dados, crie um banco de dados chamado de phone-verify e ignore esta seção. Caso contrário, instale o MySQL do site oficial para a plataforma de sua escolha. Após a instalação, ative seu terminal e execute este comando para fazer login no MySQL:

$ mysql -u {your_user_name}

OBSERVAÇÃO: adicione o sinalizador -p se você tiver uma senha para a instância do MySQL.

Depois de fazer login, execute o seguinte comando para criar um banco de dados:

mysql> create database phone-verify;
mysql> exit;

Atualize suas variáveis ambientais com suas credenciais de banco de dados. Abra o arquivo .env e faça os ajustes a seguir:

DB_DATABASE=phone-verify
DB_USERNAME={your_user_name}
DB_PASSWORD={password if any}

Como atualizar a migração e o modelo do usuário

Agora que seu banco de dados foi configurado, atualize user migrations (migrações de usuário) para criar as colunas necessárias para seus usuários. Por padrão, o Laravel cria uma migração de usuário e um Model (Modelo) quando um novo projeto é gerado. Somente alguns ajustes serão necessários para atender às necessidades deste tutorial.

Abra a pasta do projeto em seu IDE/editor de texto favorito para começar a atualizar os campos necessários na tabela de users (usuários). Abra o arquivo de migração de users (usuários) (database/migrations/2014_10_12_000000_create_users_table.php) e faça os seguintes ajustes no método up():

  public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->string('phone_number')->unique();
            $table->boolean('isVerified')->default(false);
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

Os campos phone_number e isVerified foram adicionados para armazenar o número de telefone de um usuário e verificar se o número de telefone foi verificado, respectivamente.

Execute o seguinte comando na raiz do diretório do projeto para adicionar esta tabela ao seu banco de dados:

$ php artisan migrate

Se o arquivo for migrado corretamente, você verá o nome do arquivo({time_stamp}_create_users_table) impresso na janela do console.

Agora, atualize as propriedades de fillable do modelo User (Usuário) para incluir os campos phone_number e isVerified. Abra app/User.php e faça as seguintes alterações na matriz $fillable:

/**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password', 'phone_number', 'isVerified'
    ];

Implementando a lógica de autenticação

Nesse ponto, você já configurou seu projeto Laravel com o SDK PHP da Twilio e criou seu banco de dados. Em seguida, você escreverá nossa lógica para autenticar um usuário. Primeiro, gere um AuthController que abrigará toda a lógica necessária para cada etapa de autenticação. Abra uma nova janela de console no diretório raiz do projeto e execute o seguinte comando para gerar um Controller (Controlador):

$ php artisan make:controller AuthController

O comando acima gerará um arquivo de classe de controller (controlador) em app/Http/Controllers/AuthController.php.

Como registrar usuários

Agora é hora de implementar sua lógica de autenticação. Primeiro, você implementará a lógica de registro. Vamos supor que você enviará notificações por SMS para usuários registrados a partir de seu aplicativo. Você precisará garantir que os números de telefone armazenados em seu banco de dados estejam corretos. Não há lugar melhor para aplicar essa validação do que no ponto de registro. Para isso, você usará o Twilio Verify para verificar se o número de telefone inserido pelo usuário é um número de telefone válido.

Abra app/Http/Controllers/AuthController.php e adicione o seguinte método:

/**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\User
     */
    protected function create(Request $request)
    {
        $data = $request->validate([
            'name' => ['required', 'string', 'max:255'],
            'phone_number' => ['required', 'numeric', 'unique:users'],
            'password' => ['required', 'string', 'min:8', 'confirmed'],
        ]);
        /* Get credentials from .env */
        $token = getenv("TWILIO_AUTH_TOKEN");
        $twilio_sid = getenv("TWILIO_SID");
        $twilio_verify_sid = getenv("TWILIO_VERIFY_SID");
        $twilio = new Client($twilio_sid, $token);
        $twilio->verify->v2->services($twilio_verify_sid)
            ->verifications
            ->create($data['phone_number'], "sms");
        User::create([
            'name' => $data['name'],
            'phone_number' => $data['phone_number'],
            'password' => Hash::make($data['password']),
        ]);
        return redirect()->route('verify')->with(['phone_number' => $data['phone_number']]);
    }

Veja mais de perto o código acima. Depois de validar os dados que chegam pela propriedade $request, suas credenciais da Twilio armazenadas no arquivo .env são recuperadas usando a função PHP getenv() integrada. Em seguida, eles são passados para o Twilio Client para criar uma nova instância. Depois disso, o serviço verify (verificar) é acessado na instância do Twilio Client usando:

$twilio->verify->v2->services($twilio_verify_sid)
            ->verifications
            ->create($data['phone_number'], "sms");

O serviço Twilio Verify sid também foi passado para service (serviço) que permite o acesso ao serviço Twilio Verify que você criou anteriormente neste tutorial. Em seguida, o método ->verifications->create() foi chamado passando o número de telefone a ser verificado e um canal para entrega. A senha de uso único pode ser mailsms ou call. No momento, você está usando o canal sms, o que significa que seu código OTP será enviado ao usuário via SMS. Em seguida, os dados do usuário são armazenados no banco de dados usando o método Eloquent create:

User::create([
            'name' => $data['name'],
            'phone_number' => $data['phone_number'],
            'password' => Hash::make($data['password']),
        ]);

Depois disso, o usuário é redirecionado para uma página verify que envia seu phone_number como dados para a exibição.

Como verificar a OTP do número de telefone

Após o registro do usuário, você precisará criar uma maneira de verificar a OTP enviada a ele por meio de seu channel escolhido. Crie um método verify a ser usado para verificar o número de telefone do usuário em relação ao código OTP inserido em seu formulário. Abra app/Http/Controllers/AuthController.php e adicione o seguinte método:

 protected function verify(Request $request)
    {
        $data = $request->validate([
            'verification_code' => ['required', 'numeric'],
            'phone_number' => ['required', 'string'],
        ]);
        /* Get credentials from .env */
        $token = getenv("TWILIO_AUTH_TOKEN");
        $twilio_sid = getenv("TWILIO_SID");
        $twilio_verify_sid = getenv("TWILIO_VERIFY_SID");
        $twilio = new Client($twilio_sid, $token);
        $verification = $twilio->verify->v2->services($twilio_verify_sid)
            ->verificationChecks
            ->create($data['verification_code'], array('to' => $data['phone_number']));
        if ($verification->valid) {
            $user = tap(User::where('phone_number', $data['phone_number']))->update(['isVerified' => true]);
            /* Authenticate user */
            Auth::login($user->first());
            return redirect()->route('home')->with(['message' => 'Phone number verified']);
        }
        return back()->with(['phone_number' => $data['phone_number'], 'error' => 'Invalid verification code entered!']);
    }

Assim como no método register(), os dados acima são recuperados da solicitação e instanciam o SDK da Twilio com suas credenciais antes de acessar o serviço verify. Vejamos como isso é estruturado:

$verification = $twilio->verify->v2->services($twilio_verify_sid)
            ->verificationChecks
            ->create($data['verification_code'], array('to' => $data['phone_number']));

Do exposto acima, você pode dizer que está acessando o serviço Twilio Verify como antes, mas desta vez você está fazendo uso de outro método disponibilizado por meio do serviço:

->verificationChecks->create($data['verification_code'], array('to' => $data['phone_number']));

A função create() aceita dois parâmetros, uma string do código OTP enviado ao usuário e uma matriz com uma propriedade to, cujo valor é o número de telefone do usuário para o qual a OTP foi enviada. O método verificationChecks->create() retorna um objeto que contém várias propriedades, incluindo uma propriedade booleana valid, que é ou true ou false, dependendo de a OTP inserida ser válida ou não:

if ($verification->valid) {
            $user = tap(User::where('phone_number', $data['phone_number']))->update(['isVerified' => true]);
            /* Authenticate user */
            Auth::login($user->first());
            return redirect()->route('home')->with(['message' => 'Phone number verified']);
        }
        return back()->with(['phone_number' => $data['phone_number'], 'error' => 'Invalid verification code entered!']);

Em seguida, o código verifica se a propriedade valid (válida) é verdadeira e, em seguida, prossegue para atualizar o campo isVerified do usuário para true. Em seguida, o aplicativo continua a autenticar manualmente o usuário usando o método Auth::login do Laravel, que fará login e lembrará a instância do modelo User (Usuário) fornecida.

Observação: o modelo de usuário deve implementar a interface autenticável antes que ela possa ser usada com o método Auth::login do Laravel.

Após a verificação do usuário, ele é redirecionado para o dashboard do aplicativo.

Como criar as exibições

Toda a lógica para registrar e verificar um usuário foi escrita. Agora vamos criar a exibição que o usuário usará para interagir com seu aplicativo. Será necessário um layout que sirva como a interface principal de seu aplicativo. Crie uma pasta chamada layouts em resources/views/. Em seguida, crie um arquivo chamado app.blade.php na pasta layouts. Agora abra o arquivo recém-criado (resources/views/layouts/app.blade.php) e adicione o seguinte:

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>{{ config('app.name', 'Laravel') }}</title>
    <!-- Styles -->
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
</head>
<body>
    <div id="app">
        <nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
            <div class="container">
                <a class="navbar-brand" href="{{ url('/') }}">
                    {{ config('app.name', 'Laravel') }}
                </a>
                <button class="navbar-toggler" type="button" data-toggle="collapse"
                    data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"
                    aria-label="{{ __('Toggle navigation') }}">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <!-- Left Side Of Navbar -->
                    <ul class="navbar-nav mr-auto">
                    </ul>
                    <!-- Right Side Of Navbar -->
                    <ul class="navbar-nav ml-auto">
                        <!-- Authentication Links -->
                        @guest
                        <li class="nav-item">
                            <a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
                        </li>
                        @else
                        <li class="nav-item dropdown">
                            <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button"
                                data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
                                {{ Auth::user()->name }} <span class="caret"></span>
                            </a>

                        </li>
                        @endguest
                    </ul>
                </div>
            </div>
        </nav>
        <main class="py-4">
            @yield('content')
        </main>
    </div>
</body>
</html>

Nota: para agilizar a criação deste aplicativo, o Bootstrap está sendo utilizado para criar seu aplicativo e formulários.

Em seguida, crie uma pasta chamada auth em resources/views/. Agora, crie os seguintes arquivos e cole-os em seu respectivo conteúdo. Em resources/views/auth/register.blade.php:

@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">{{ __('Register') }}</div>
                <div class="card-body">
                    <form method="POST" action="{{ route('register') }}">
                        @csrf
                        <div class="form-group row">
                            <label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label>
                            <div class="col-md-6">
                                <input id="name" type="text" class="form-control @error('name') is-invalid @enderror" name="name" value="{{ old('name') }}" required autocomplete="name" autofocus>
                                @error('name')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>
                        <div class="form-group row">
                            <label for="phone_number" class="col-md-4 col-form-label text-md-right">{{ __('Phone Number') }}</label>
                            <div class="col-md-6">
                                <input id="phone_number" type="tel" class="form-control @error('phone_number') is-invalid @enderror" name="phone_number" value="{{ old('phone_number') }}" required>
                                @error('phone_number')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>
                        <div class="form-group row">
                            <label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">
                                @error('password')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>
                        <div class="form-group row">
                            <label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm Password') }}</label>
                            <div class="col-md-6">
                                <input id="password-confirm" type="password" class="form-control" name="password_confirmation" required autocomplete="new-password">
                            </div>
                        </div>
                        <div class="form-group row mb-0">
                            <div class="col-md-6 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    {{ __('Register') }}
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

e agora em resources/views/auth/verify.blade.php:

@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">{{ __('Verify Your Phone Number') }}</div>
                <div class="card-body">
                    @if (session('error'))
                    <div class="alert alert-danger" role="alert">
                        {{session('error')}}
                    </div>
                    @endif
                    Please enter the OTP sent to your number: {{session('phone_number')}}
                    <form action="{{route('verify')}}" method="post">
                        @csrf
                        <div class="form-group row">
                            <label for="verification_code"
                                class="col-md-4 col-form-label text-md-right">{{ __('Phone Number') }}</label>
                            <div class="col-md-6">
                                <input type="hidden" name="phone_number" value="{{session('phone_number')}}">
                                <input id="verification_code" type="tel"
                                    class="form-control @error('verification_code') is-invalid @enderror"
                                    name="verification_code" value="{{ old('verification_code') }}" required>
                                @error('verification_code')
                                <span class="invalid-feedback" role="alert">
                                    <strong>{{ $message }}</strong>
                                </span>
                                @enderror
                            </div>
                        </div>
                        <div class="form-group row mb-0">
                            <div class="col-md-6 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    {{ __('Verify Phone Number') }}
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Por fim, crie uma página para a qual os usuários verificados serão levados criando um arquivo chamado home.blade.php em resources/views/. Adicione o seguinte conteúdo (resources/views/home.blade.php):

@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">Dashboard</div>
                <div class="card-body">
                    @if (session('message'))
                        <div class="alert alert-success" role="alert">
                            {{ session('message') }}
                        </div>
                    @endif
                    You are logged in!
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

Como atualizar as rotas

Incrível! Agora que você concluiu a criação da exibição, vamos atualizar o arquivo routes/web.php com as rotas necessárias para o aplicativo. Abra routes/web.php e faça as seguintes alterações:

<?php
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
 */
Route::get('/', function () {
    return view('auth.register');
})->name('register');

Route::get('/verify', function () {
    return view('auth.verify');
})->name('verify');

Route::get('/home', function () {
    return view('home');
})->name('home');

Route::post('/', 'AuthController@create')->name('register');
Route::post('/verify', 'AuthController@verify')->name('verify');

Como testar o aplicativo

Agora que você terminou de criar o aplicativo, vamos testá-lo. Abra a janela do console e navegue até o diretório do projeto e execute o seguinte comando:

$ php artisan serve

Ele mostrará o aplicativo Laravel em uma porta localhost, que normalmente é 8000. No navegador, abra o link localhost que aparece depois da execução do comando e você verá uma página de registro semelhante a esta:

Aplicativo Laravel

Preencha o formulário de registro para acionar o envio de um código OTP. Você usará esse código ao preencher o formulário na página para a qual foi redirecionado.

GIF Formulário de Registro em Laravel

Conclusão

Ótimo! Ao concluir este tutorial, você aprendeu a usar o serviço de verificação da Twilio para validar números de telefone em um aplicativo Laravel. Além disso, aprendemos a autenticar manualmente um usuário em um aplicativo do Laravel. Se você quiser dar uma olhada no código-fonte completo deste tutorial, ele está disponível no Github.

Este artigo foi traduzido do original "How to Verify Phone Numbers in a Laravel PHP Application with Twilio Verify". Enquanto melhoramos nossos processos de tradução, adoraríamos receber seus comentários em help@twilio.com - contribuições valiosas podem render brindes da Twilio.