Phone Verification via Voice with Laravel, Twilio, S3 and AWS Polly

March 19, 2019
Written by
Gbenga Oni
Contributor
Opinions expressed by Twilio contributors are their own

phone-verification-aws-polly-twilio-cover-photo.png

Twilio offers an array of products and solutions that enable you to engage your customers. One of which is Programmable Voice that allows you to make and receive calls, as well as monitor calls, using the Voice API.

In this tutorial, you will learn how to use Twilio’s Programmable Voice API to implement a phone number verification system that places a voice call to the user’s phone from your PHP Laravel Application. By the end of the tutorial, you will have developed a PHP Laravel Application with an authentication system that can verify a user’s phone number, by placing a call through to it.

Requirements

  1. PHP environment running 7.3+
  2. Laravel
  3. A Twilio Account
  4. An AWS Account and S3 bucket

Setup Laravel and Twilio PHP SDK

Get Composer

Firstly, we need to download Composer, a PHP package manager. We will use Composer to install Laravel and other dependencies. If you don’t already have it installed, you can download it here.

Install Laravel

We will be building a Laravel application in order to interact with the Twilio API. Create a fresh Laravel application using composer by running the following command.

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

Start Laravel

Now that we have Laravel installed, start Laravel on the local server:

$ cd myLaravelTwilioApp && php artisan serve

The Laravel Application should now be running on http://localhost:8000. Check it out in your browser to see!

Download Twilio PHP SDK

Now that we have our Laravel app setup, we need a way to include the Twilio API within our project. The Twilio PHP SDK will help us interact with the Twilio API from within our Laravel App. We will use Composer — the recommended method — to install the SDK.

$ composer require twilio/sdk

The Twilio PHP SDK is now ready for use. You can verify this by looking inside of your vendor folder at vendor/twilio.

Setup Twilio

If you haven’t already,  sign up for Twilio and get a voice-enabled phone number.

NOTE: If you have already signed up for Twilio and you have a Twilio phone number that’s voice-enabled, you can skip this step and the following, “Get a Twilio Phone Number”.

Navigate to https://www.twilio.com/try-twilio in your browser to get started with a free Twilio account. Fill out the form accordingly and click “Get Started”.

In order to verify your identity, and to also make calls to it from Twilio, you will need to verify your personal phone number. Provide your number and click “Verify”.

You should receive a code on your phone. Provide the code and proceed. Now that you’ve successfully verified your phone number, you will be prompted to create a project. Select the “Learn and Explore” option, name your project and click “Skip Remaining Steps”.

On successful creation of your project, you will be taken to your project dashboard.

Get a Twilio Phone Number

If you don’t have a Twilio phone number that’s voice-enabled, you need to purchase one.

Navigate to the “Get a Number” page ( and check that you want a  voice capable number in your search.

You will be provided with a list of voice-enabled Twilio phone numbers available for purchase.

Make a choice from the available numbers list by clicking “Buy”. It will then be attached to your account.

You have now successfully set up a Twilio account and also purchased a Twilio voice-enabled phone number.

Make a Test Call to Your Phone

To confirm our Voice API is ready for use, we will make a test call to the phone number we initially verified. Create a Verification Controller in your Laravel Application by running the following command in your terminal:

$ php artisan make:controller VerifyController

Now edit the VerifyController::call() function to look like this:

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Twilio\Rest\Client;

class VerifyController extends Controller
{
  public function call(){
   // Your Account SID and Auth Token from twilio.com/console
   $account_sid = env('TWILIO_ACCOUNT_SID');
   $auth_token = env('TWILIO_AUTH_TOKEN');
   // In production, these should be environment variables. E.g.:
   // $auth_token = $_ENV["TWILIO_ACCOUNT_SID"]
   // A Twilio number you own with Voice capabilities
   $twilio_number = env('TWILIO PHONE NUMBER');

   // Where to make a voice call (your cell phone?)
   $to_number = ('YOUR VERIFIED PHONE NUMBER');
   $client = new Client($account_sid, $auth_token);

   $client->account->calls->create(
       $to_number,
       $twilio_number,
       array(
           "url" => "http://demo.twilio.com/docs/voice.xml"
       )
   );
   }
}

NOTE: Be sure to replace your Twilio credentials with the <TWILIO ACCOUNT SID> and <TWILIO AUTH TOKEN> placeholders. Also Replace the <YOUR VERIFIED PHONE NUMBER> placeholder with your verified phone number.

Create a Route /call with the GET access methodThe /call route routes the GET request to the VerifyController::call()that makes a test call to the number value assigned to the $twilio_number variable.

Route::get('/call', 'VerifyController@call');

Now navigate to http://localhost:8000/call in your browser. You should get a call on your previously verified personal number.

Create Verification Middleware

We need to create a middleware that will validate, upon login, that a user’s phone number is verified. If valid, the request is processed, if not, the user will be redirected to a page to verify their phone number.

Create Laravel Authentication Scaffold

First, we need to create an authentication scaffold for our Laravel App. Make sure you have provided Laravel the right database credentials for your local database, through the .env file in your project folder.

$ php artisan make:auth

Before migrating the database files, we need to update the default Laravel users table columns to include additional columns for phone (user’s phone number) and phone_verified_at (the time the phone number was verified). We will do this by editing the database/migrations/2014_10_12_000000_create_users_table.php file and adding the following to the column declarations:

$table->string('phone');
$table->timestamp('phone_verified_at')->nullable();

Migrate the database files to create our users table with the updated columns:

Be sure to update the database credentials in the .env file before running the migration command.

$ php artisan migrate

Update the fillable property of the User model app/User.php to look something like this:

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

The above update adds the phone and phone_verified_at properties to the fillable properties of the User model. That is, they can be assigned values by Laravel.

Now update the RegisterController in the App/Http/Controllers/Auth folder. This ensures the phone data is stored in the User table, in the database.

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

Update the register.blade.php view with an input element for user phone number at the registration point:

<div class="form-group row">
   <label for="phone" class="col-md-4 col-form-label text-md-right">{{ __('Phone Number') }}</label>

   <div class="col-md-6">

   <input id="phone" type="number" 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>

Create Laravel Middleware

Middleware helps filter HTTP requests to the Laravel Application. We will create a middleware that checks that the user making a certain request has their phone number verified.

$ php artisan make:middleware EnsurePhoneIsVerified

This will create middleware in the App/Http/Middleware folder with the name EnsurePhoneIsVerified.php

Now lets register the EnsurePhoneIsVerified.php middleware in our App/Http/kernel.php file. By listing the middleware class in the $routeMiddleware property of the App/Http/kernel.php class, the EnsurePhoneIsVerified.php middleware will be enabled to run during all HTTP requests to the selected routes within Laravel.

<?php
   namespace App\Http;
   use Illuminate\Foundation\Http\Kernel as HttpKernel;
  
   class Kernel extends HttpKernel{   
       ....   
       ....   
       protected $routeMiddleware = [ 
    
       ....       
       'verified-phone' => \App\Http\Middleware\EnsurePhoneIsVerified::class,
       ];
   }

Open the middleware EnsurePhoneIsVerified.php, it should look like this:

public function handle($request, Closure $next)
{
   if (is_null(\Auth::user()->phone_verified_at)) {
    return redirect()->route('verify-phone', \Auth::user()->email);
   }
   return $next($request);
}

This is a BeforeMiddleware that runs before a user’s request is processed. The middleware redirects the user to the verify-phone route if their phone number is yet to be verified.

Be sure to add $this->middleware(‘verified-phone’); to the construct method of the HomeController in order to trigger the verified-phone middleware.

Let’s create a route to the appropriate VerifyController method that will display the verification page. This is the page the EnsurePhoneIsVerified.php middleware will redirect unverified users to.

Route::get('/verify-phone', 'VerifyController@index')->name('verify-phone');

The VerifyController index method should look something like this:

public function index()
{
   $user = \Auth::user();
   return view('auth.verify-phone-index', compact('user'));
}

It displays the view file in resources/views/auth/verify-index.blade.php. The verify-index.blade.php file displays a form with a “Call me with code” button. Let’s create this file and add the following code:

@extends('layouts.app')
  @section('content')
    <div class="container">
      <div class="row justify-content-center">
        <div class="col-md-8">
          <div class="card">
            @if (session('status'))
              <div class="alert alert-success">
                {{ session('status') }}
               </div>
           @endif
         <div class="card-header">{{ __('Verify Your Phone Number') }}</div>
     <div class="card-body">
     {{ __('We will call you phone number with a verification code.') }}
       <form method="POST" action="{{ route('call-phone') }}">
        @csrf
          <div class="form-group row mb-0">
            <div class="col-md-8 offset-md-4">
              <button type="submit" class="btn btn-primary">
                {{ __('Call me with Code') }}
              </button>
            </div>
           </div>
         </form>
      </div>
     </div>
    </div>
   </div>
</div>
@endsection

The form points to a call-phone route which points to the VerifyController@callPhone function that places a call through to the currently logged in, unverified user’s phone number. Let’s add the call-phone route:

Route::post('/verify-phone', 'VerifyController@callPhone')->name('call-phone');

Now create a callPhone function in the VerifyController controller:

public function callPhone(Request $request)
{
   $user = \Auth::user();
   $account_sid = env('TWILIO ACCOUNT SID');
   $auth_token = env('TWILIO AUTH TOKEN');
   $twilio_number = ('TWILIO PHONE NUMBER');
   $to_number = $user->phone;
   $client = new Client($account_sid, $auth_token);
   $client->account->calls->create(
       $to_number,
       $twilio_number,
       array(
           "url" => "http://demo.twilio.com/docs/voice.xml"
       )
   );
   return redirect()->back();
}

The code above places a dummy call to the phone number attached to the user with the Twilio Voice API.

Generate MP3 Speech with verification code using AWS Polly

Now that our Laravel App can prompt unverified users to verify their phone numbers, and also place a dummy phone call to them, we need to provide the Voice API with relevant speech containing the verification code of the user. This is where AWS Polly comes in, an AWS Service that turns text into life-like speech.

Install the AWS PHP SDK using composer

We will use Composer to install the AWS PHP SDK on our Laravel Application:

$ composer require aws/aws-sdk-php-laravel

Once that’s done, register the AWS Service Provider in the providers key in config/app.php

'providers' => [
      // ...
      Aws\Laravel\AwsServiceProvider::class,
]

Also find the aliases key and add:

'aliases' => [
      // ...
      'AWS' => Aws\Laravel\AwsFacade::class,
]

Next you need to create an IAM user with access to AWS Polly and S3 and also an Access Key ID and a Secret Key for that user. Once complete, add the generated Access Key ID and Secret Key to the `.env` environment file for Laravel:

AWS_ACCESS_KEY_ID=<YOUR AWS ACCESS KEY ID>
AWS_SECRET_ACCESS_KEY=<YOUR AWS SECRET KEY>
AWS_REGION=<YOUR AWS REGION>
AWS_BUCKET=<YOUR AWS BUCKET NAME>

Generating Text Speech with AWS PollyFirst we need to add two functions to the VerifyController:
A function to generate a verification codeA function to convert text to speechAdd the generateVerificationCode() function to the VerifyController.

private function generateVerificationCode(): string
{
  $count = 0;
  $code = '';
  while ($count < 5) {
      $code .= mt_rand(0, 9);
      $count++;
  }
  return chunk_split( $code, 1, ' ' );
}

Now add the convertTextToSpeech() function to the VerifyController

private function convertTextToSpeech(string $text)
{
  $polly = \AWS::createClient('polly');
  $result_polly = $polly->synthesizeSpeech([
      "OutputFormat" => "mp3",
      "Text" => $text,
      "TextType" => "text",
      "VoiceId" => "Amy"
  ]);
  $resultData_polly = $result_polly->get("AudioStream")->getContents();
  $file_name = time()."-polly.mp3";
  $s3 = \AWS::createClient('s3');
  $result_s3 = $s3->putObject(array(
      'Bucket'     => '<YOUR AWS BUCKET NAME>',
      'Key'        => $file_name,
      "ACL" => "public-read",
      "Body" => $resultData_polly,
      "ContentType" => "audio/mpeg"
  ));
  return $result_s3['ObjectURL'];
}

The convertTextToSpeech() function takes a string as an argument. Then it initiates AWS Polly and creates a speech based on the text. The resulting speech is then saved into an S3 bucket with public read access. Finally, it returns the URL of the S3 bucket containing the speech.

Call User with Code

Now we’ll update the CallPhone function which will generate a verification code:

public function callPhone(Request $request)
{
  $user = \Auth::user();


  $account_sid = '<TWILIO ACCOUNT SID>';
  $auth_token = '<TWILIO AUTH TOKEN>';
  $twilio_number = "<TWILIO PHONE NUMBER>";


  $code = $this->generateVerificationCode();
  $request->session()->put('verification-code', $code);
  $message = "Hello. Your verification code is: {$code}. Goodbye.";


  $file_url = $this->convertTextToSpeech($message);
  $to_number = "<YOUR COUNTRY CODE>".ltrim($user->phone, $user->phone[0]);
  $client = new Client($account_sid, $auth_token);
  $client->account->calls->create(
      $to_number,
      $twilio_number,
      array(
          "method" => "GET",
          "url" => $file_url
      )
  );
  return redirect()->back();
}

We have updated the callPhone() function to generate a verification code, save the code in the user’s session, generate a message and to convert this message into a speech.

The file URL of the generated speech is then sent to Twilio Voice API when initiating a call. This is the speech that gets played to the user.

NOTE: You need to be a paid user if you want Twilio Voice API to play any speech other than the default one. You will also then be able to make calls to other numbers apart from yours.

Create a function to receive and verify sent code

We need to create an additional page where the user will provide the verification code read to them on the phone. We will also create a function that takes this code, checks its authenticity and verifies the user accordingly.

First, edit the callPhone() function to display a form, instead of redirecting back:

// return redirect()->back();
return view('auth.verify-phone-form');

Create the resources/views/auth/verify-phone-form.blade.php file. This page displays a form where the user will provide the verification code read to them during the phone call:

@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">
                   {{ __('Enter the Verification code read to you on the phone.') }}
               <form method="POST" action="{{ route('verify-code') }}">
                @csrf
                  <div class="form-group row">
                   <label for="code" class="col-md-4 col-form-label text-md-right">{{ __('Code') }}</label>
                     <div class="col-md-6">
           <input id="code" type="number" class="form-control{{        $errors->has('code') ? ' is-invalid' : '' }}" name="code" value="" required>
              </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

Now create a verify-code route in routes/web.php:

Route::post('/verify-code', 'VerifyController@verify')->name('verify-code');

Create a verify() function in VerifyController:

public function verify(Request $request)
{
  $user = \Auth::user();
  $code = $request->session()->get('verification-code');
 
  if( $code === $request->code){
      $user->phone_verified_at = date("Y-m-d H:i:s");
      $user->save();
      return redirect()->route('home')->with('status','Phone number successfully verified!');
  }
  return redirect()->back()->with('status', 'Code incorrect!');
}

The verify function receives the sent code previously saved in the user's session, then checks if it matches the code provided by the user. If there’s a match, it updates the user’s phone_verified_at column in their record with the current timestamp, signifying the user is now verified. It then redirects them to the home page with a success message. If the code provided does not match the one sent, the user is redirected back with a failure message.

Testing

To test the system, open the website on your localhost:

  1. Attempt to register or login
  2. You will be taken to a verification page, click on the “Call me with code” button
  3. Wait for Twilio to call your phone playing the speech containing the code
  4. Once you’ve gotten the code, provide it in the form, then click the “Verify Phone Number” button.

Conclusion

You have successfully completed this tutorial. You can now:

  1. Integrate Twilio with a Laravel Application
  2. Consume the Twilio Voice RESTFul API
  3. Make calls to users using the Voice API
  4. Implement, with Twilio Voice, phone number verification on your PHP Laravel App

Next, you should try to use TwiML in the stead of AWS Polly to read out the text to your user during outbound calls. If you have questions, you can reach me on the following networks:

Twitter: @Gbxnga
Github: gbxnga
Website: https://gbengaoni.com