Providing One Time Passwords via Voice Calls with Twilio and Laravel

March 08, 2019
Written by
Abraham Omadoye
Contributor
Opinions expressed by Twilio contributors are their own

one-time-voice-calls-with-twilio-voice-cover-photo.png

One time passwords (OTP’s) provide an extra security layer for applications, and in this tutorial we will set up a system to provide one time passwords to users via voice calls powered by Twilio’s amazing programmable voice SDK and Laravel.

After you’re finished you will have developed an OTP system that can serve as an extra security layer for various actions in your application. The OTP could be used for authentication, purchase actions or whatever actions you consider worthy of an extra layer of authorisation in your application.

Requirements

For this tutorial, we’ll assume the following:

  • You are familiar with the terminal (command line)
  • You have Composer globally installed
  • You are familiar with PHP and Laravel and have PHP 7.3.2+ installed
  • You can create a Laravel project.
  • You either have MySQL locally installed, or are able to use docker and docker-compose (see the “Database Considerations” section below for more on this)

Create a Laravel Project

First off we will be creating a new Laravel project.

$ laravel new twilio-voice-otp

Once the Laravel application is created, enter into the project directory and let the fun begin.

$ cd twilio-voice-otp

Database Considerations

Our application is going to need a database to store the OTP’s and handle whatever persistent data needs we might have. However, setting up the database and connecting it to our Laravel app is outside the scope of this tutorial, Laravel’s documentation provides instructions on how to connect to a database from a Laravel application. Ensure that you have your local database setup and connected to the app before we go on. If you’re interested in using Docker, follow the next steps, otherwise skip to “Twilio Account Setup”.

NOTE: If you prefer using Docker, you can copy the Dockerfiles (app.docker and web.docker) and the docker-compose setup found in the accompanying repository for this tutorial. Note that you also have to copy the vhost.conf file into your project directory as its configuration for the nginx server spun up by the docker-compose stack.

To start up the docker-compose stack run the following command:

$ docker-compose up

This command sets up everything needed to run the application and exposes the app at Localhost:1000.

Twilio Account Setup

Now that you have your Laravel application created and have connected it to your local database, you need to set up a Twilio account so you can use the Twilio Programmable Voice SDK as needed. Follow through the following steps to setup your Twilio account and begin using the Programmable Voice SDK.

  1. Create an account with Twilio
  2. Verify your phone number as requested by Twilio. You need to link a Twilio verified phone number to your account so that you can use it in testing Twilio’s various product offerings, including receiving the phone calls made in this tutorial.
  3. Set up a Twilio phone number that can handle Voice. This is a different phone number from the one we verified in step 2 above. Twilio phone numbers are set up to handle various tasks like making phone calls, sending SMS messages, etc., whereas the verified phone number was set up so it could receive phone calls and messages.

NOTE: Once you upgrade your Twilio account you can make calls and send messages to other phone numbers including the verified phone number.

To set up a Voice enabled Twilio phone number, go to your console, and click on the “Get a Trial Number” button.

If you followed the default prompts, you should now have a USA Twilio phone number capable of making voice calls to your verified account phone number. Next up we’ll take a look at Twilio’s programmable voice SDK and how we can use it in Laravel.

Account Credentials

Before we begin writing code, there are some environment variables we require from our Twilio account console to authorize programmatic use of Twilio’s services from our application:

   1. Account SID
   2. Auth Token
   3. Trial Number
   4. Authorised Phone Number (The phone number you authorized during account creation)

Create the environment variables below in your .env file, and ensure to replace the placeholder values with your actual account values. We will use these later to authorize the Programmable Voice SDK to make phone calls from our application.

TWILIO_SID=*****************
TWILIO_AUTH_TOKEN=******************
TWILIO_FROM_NUMBER=*****************
AUTHORIZED_PHONE_NUMBER=************

Twilio’s Programmable Voice SDK

Now that we have our Laravel app and Twilio voice phone number setup, we need to link them up so our Laravel application is able to make phone calls using Twilio, enter Twilio’s Programmable Voice SDK.

Twilio Programmable Voice allows you to make and receive phone calls in your application, alongside some other cool features like creating conference calls and transcribing call recordings. For this tutorial we only need to make phone calls from our Laravel application. Twilio has an amazing PHP SDK which we will use to drastically simplify the process of making phone calls from our application.

To get started, we'll install the Twilio PHP helper library. From your terminal, run the following command in your project directory:

$ composer require twilio/sdk

Create The View That Requests an OTP

Next we will create a simple view page with a button which requests a one time password from the system. Create a blade file called create.blade.php in resources/views/otp and copy the following code snippet:

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Create OTP</title>

    <!-- Fonts -->
    <link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet">

    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css">

    <!-- Styles -->
    <style>
        html, body {
            background-color: #fff;
            color: #636b6f;
            font-family: 'Nunito', sans-serif;
            font-weight: 200;
            height: 100vh;
            margin: 0;
        }

        .container {
            max-width: 600px;
            margin: 100px auto;
        }

    </style>
</head>
<body>
<div class="container">
    @if(isset($otpValidated) && $otpValidated)
        <div class="alert alert-success alert-dismissible">
            <a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>
            <strong>Success!</strong> Your OTP Code {{$otpCode}} has been validated.
        </div>
    @endif

        @isset($error)
            <div class="alert alert-danger alert-dismissible">
                <a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>
                <strong>Error!</strong> {{$error}}
            </div>
        @endif

    <form method="post" action="{{route('otp.store')}}">
        @csrf
        <input type="submit" value="Request For OTP" class="btn btn-lg btn-info">
    </form>
</div>

<!-- jQuery library -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<!-- Latest compiled JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/js/bootstrap.min.js"></script>
</body>
</html>

The create.blade.php view simply displays a button requesting for an OTP from the system. The 'otp.store' route which it posts to will be implemented shortly.

Generate the OTPController

Now that we have created the view that requests our OTP codes, let's begin to flesh out the services, controllers and routes that handle OTP generation.

Run the command below to create the resourceful OTPController which will control several actions to be implemented.

$ php artisan make:controller OTPcontroller --resource

Next, we add the resourceful route to routes/web.php

Route::resource('otp', 'OTPController');

NOTE: The Laravel documentation explains resourceful routes and controllers in detail.

After creating resourceful routes and an OTPController, we need to make some changes to the OTPController. Replace the contents of your OTPController with the code below:

<?php

namespace App\Http\Controllers;

use App\Services\OTPService;
use App\Services\TwilioService;
use Illuminate\Http\Request;

class OTPController extends Controller
{
    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('otp.create');
    }

    
    /**
     * Route which Twilio calls to determine the voice message to be played to the user.
     *
     * @param Request $request
     * @param $otpCode
     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
     */
    public function generateVoiceMessage(Request $request, $otpCode, TwilioService $twilioService)
    {
        $twimlResponse = $twilioService->generateTwimlForVoiceCall($otpCode);

        return response($twimlResponse, 200)
            ->header('Content-Type', 'text/xml');
    }

    public function validateOTP(Request $request, OTPService $OTPService)
    {
        $otpCode = $request->get('otpCode');

        $otpIsValid = $OTPService->otpIsValid($otpCode);

        if($otpIsValid) {
            return view('otp.create', ['otpValidated' => true, 'otpCode' => $otpCode]);
        }

        return view('otp.validate', ['error' => $otpCode . " is invalid"]);
    }
}

As you can see, we've updated OTPController@create to display the create OTP page to the user, and the following methods were added to the OTPController:

  1. 1. **generateVoiceMessage**

    Twilio requires that we provide a url where it can get the TwiML that describes the call to be made. This function generates the TwiML response which includes the OTP code.

  2. 2. **validateOTP**

    This function handles a POST request to validate an OTP that a user provides and redirects to the user to an appropriate page providing feedback depending on whether the OTP validation was successful or not.

We also need to add a generateVoiceMessage() function to the OTP controller. This method takes in the $otpCode and generates the TwiML code that defines the call which the user will receive.

NOTE: TwiML (the Twilio Markup Language) is a set of instructions you can use to tell Twilio what to do when you receive or make a call, SMS, or fax.

Update Routes

Ensure that routes/web.php has the following routes which power our application.

<?php

Route::get('/', function () {
    return view('welcome');
});

Route::post('generateMessage/{otpCode}', 'OTPController@generateVoiceMessage')->name('generateMessage');

Route::resource('otp', 'OTPController');

Route::get('otp/validate', 'OTPController@ShowValidate');

Route::post('otp/validate', 'OTPController@validateOTP')->name('otp.validate');

After updating our routes, we need to remove CSRF protection from the **generateMessage** route so that an external party (Twilio) can POST to that endpoint. Update the $except property in app/Http/Middleware/VerifyCsrfToken.php to the following.

 /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        'generateMessage/*'
    ];

### Create OTP Model and Database Migrations

In order for us to create OTP’s in the database, we will create a model and migrations for our OTP’s. Run the following commands to create the model and migrations:

$ php artisan make:migration create_otps_table --create=otps
$ php artisan make:model OTP

Replace the up() method of the newly created migration with the following code:

     /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('otps', function (Blueprint $table) {
            $table->increments('id');
            $table->string('code');
            $table->boolean('active');
            $table->timestamps();
        });
    }

Next, run the migrate command:

$ php artisan migrate

Add the fillable properties on the OTP model, and add the table name also.

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class OTP extends Model
{
    protected $table = "otps";

    protected $fillable = ['code', 'active'];
}

Create OTPService for Creating and Validating OTPs

Next we create an OTP service that is used for the creation and validation of OTP codes. Create a file app/Services/OTPService.php and paste the following code snippet:

<?php

namespace App\Services;

use App\OTP;

class OTPService
{
    /**
     * @var TwilioService
     */
    private $twilioService;

    public function __construct(TwilioService $twilioService)
    {
        $this->twilioService = $twilioService;
    }

    /**
     * Create the OTP and make a call to the user providing the code
     *
     * @throws \Twilio\Exceptions\TwilioException
     */
    public function createOtp()
    {
        $otp = OTP::create([
            'code' => rand(10000, 99999),
            'active' => 0,
        ]);

        return $otp;
    }

    /**
     * Validate an OTP provided by a user
     *
     * @param $otpCode
     * @return mixed
     */
    public function otpIsValid($otpCode)
    {
        return count(OTP::where('code', $otpCode)->get()) ? true : false;
    }
}

The OTPService handles creating the new OTP in the database, and also validating an OTP.

Creating the Twilio Service for Voice Calls

We now need a service that our application uses to make the voice calls via the Twilio PHP helper library. Create app/Services/TwilioService.php and paste the following code into it.

<?php

namespace App\Services;

use Twilio\Exceptions\ConfigurationException;
use Twilio\Rest\Client;
use Twilio\TwiML\VoiceResponse;

class TwilioService
{
    /**
     * Twilio rest client
     *
     * @var Twilio\Rest\Client
     */
    private $twilioRestClient;

    /**
     * TwilioService constructor.
     *
     * @throws ConfigurationException
     */
    public function __construct()
    {
        $this->twilioRestClient = new Client(env('TWILIO_SID'), env('TWILIO_AUTH_TOKEN'));

    }

    /**
     * Make a voice call to a phone number providing an otp code from the application
     *
     * @param String $phoneNumber
     * @param $otpCode
     * @return \Twilio\Rest\Api\V2010\Account\CallInstance
     * @throws \Twilio\Exceptions\TwilioException
     */
    public function makeOtpVoiceCall(String $phoneNumber, $otpCode)
    {
        // Url which points to our application route for generating the TWIML used in calling the user.
        $twimlUrl = env('APP_URL') . "/generateMessage/" .$otpCode;

        $call = $this->twilioRestClient->calls->create($phoneNumber, env('TWILIO_FROM_NUMBER'), array("url" => $twimlUrl));

        return $call->sid;
    }

    /**
     * Generate the TWIML needed for a voice call sending an OTP code to a user.
     *
     * @param $otpCode
     * @return VoiceResponse
     */
    public function generateTwiMLForVoiceCall($otpCode)
    {
        /**
         * We add spaces between each digit in the otpCode so Twilio pronounces each number instead of pronouncing the whole word.
         *
         * @See https://www.twilio.com/docs/voice/twiml/say#hints
         */
        $otpCode = implode(' ', str_split($otpCode));

        $voiceMessage = new VoiceResponse();
        $voiceMessage->say('This is an automated call providing you your OTP from the test app.');
        $voiceMessage->say('Your one time password is ' . $otpCode);
        $voiceMessage->pause(['length' => 1]);
        $voiceMessage->say('Your one time password is ' . $otpCode);
        $voiceMessage->say('GoodBye');

        return $voiceMessage;
    }
}

As seen above, we create an instance of the Twilio Rest Client when our service class is instantiated. This rest client instance uses the account SID and auth token we stored in our .env file to authorize the Twilio library to carry out actions on behalf of your Twilio account.

The makeOtpVoiceCall function does exactly what the name signifies. It makes a call to a provided phone number with a predefined message which will include the OTP code.
The generateTwiMLForVoiceCall function generates the TwiML XML that describes the call which will be placed to the user.

NOTE: If you're using a Twilio trial account, you can only make outgoing calls to phone numbers you have verified with Twilio. Phone numbers can be verified with your Twilio Console’s Verified Caller IDs.

Storing OTP’s and Making the Phone Call

Now that we have created the OTPService, and the TwilioService, update the store method in the app/Http/Controllers/OTPController.php to create an OTP and make a call to the user once an OTP is created:

 /**
     * Store a newly created resource in storage.
     *
     * @param Request $request
     * @param OTPService $OTPService
     * @param TwilioService $twilioService
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
     * @throws \Twilio\Exceptions\TwilioException
     */
    public function store(Request $request, OTPService $OTPService, TwilioService $twilioService)
    {
        $otp = $OTPService->createOtp();

        $callId = $twilioService->makeOtpVoiceCall(env('AUTHORIZED_PHONE_NUMBER'), $otp->code);

        return view('otp.validate', ['callId' => $callId]);
    }

The store method above carries out the following actions:

  1. Creates and stores an OTP using the OTPService
  2. Places a call to the user with the OTP using the TwilioService
  3. Upon success of the call, redirect to an OTP validation page, passing in the call ID of the call made to the user providing the OTP.

Creating the OTP Validation View

After creating the OTP and making the call to the user in the previous step, we redirected to an OTP validation view. Let’s create this view page now. Create validate.blade.php in resources/views/otp, and paste the following code snippet into it:

<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Validate OTP</title>

    <!-- Fonts -->
    <link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css">

    <!-- Styles -->
    <style>
        html, body {
            background-color: #fff;
            color: #636b6f;
            font-family: 'Nunito', sans-serif;
            font-weight: 200;
            height: 100vh;
            margin: 0;
        }

        .container {
            max-width: 600px;
            margin: 20px auto;
        }

    </style>
</head>
<body>
<div class="container">
    <h2>Validate your One Time Password</h2>

    @isset($callId)
        <div class="alert alert-info alert-dismissible">
            <a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>
            Call with ID {{$callId}} has been made to your authorised phone number.
        </div>
     @endisset

    <form method="post" action="{{route('otp.validate')}}">
        @csrf
        <div class="form-group">
            <input type="number" name="otpCode" placeholder="Enter the OTP provided to you" class="form-control" required>
        </div>

        <input type="submit" value="Validate OTP" class="btn btn-success">
    </form>
</div>

<!-- jQuery library -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<!-- Latest compiled JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/js/bootstrap.min.js"></script>
</body>
</html>

This page displays a form that asks the user to validate an OTP, and posts our OTP validation route upon validation.

Expose our Application Online using Ngrok

Remember when we said Twilio needs to query a url for the TwiML that describes the call to be made?

Well we are about to test our application but there's just one problem, our application is deployed locally. This works for viewing pages and carrying out actions from our system, but Twilio also needs to access the generateMessage route we defined over the Internet.

There are a number of services which facilitate exposing your localhost to the Internet, but one that we use in this tutorial is ngrok. ngrok is a free tool that lets you put the web app running on your local machine on the Internet.  

Follow the official ngrok documentation to download and setup ngrok on your system.

Next, expose your application to the Internet by running the following command (replace 1000 with whatever port your app is running on).

$ ngrok http 1000

After running the ngrok command, you get an ngrok URL which your local app is now accessible via the Internet. Replace the APP_URL in your .env file with the ngrok URL.

// This is just a random test ngrok url. Replace it with yours
APP_URL=http://3d3215a.ngrok.com

Testing the Application

If you've been able to get this far in the tutorial congratulations, we are done with the coding and what remains is to enjoy your wonderful new OTP system.

Navigate to the OTP creation route i.e otp/create to see the OTP request page





You can now request for an OTP, get a call and validate the OTP. Congratulations.

Conclusion

Because this is a tutorial focusing on Twilio Voice and Laravel, some implementation and security considerations were intentionally overlooked to reduce the scope of the tutorial.
Things like OTP invalidation after a certain duration, deactivation of the OTP upon validation, linking of OTP's to the user who created them e.t.c were not included, so if you're building a production-ready system, take care to implement these features.

There you have it, a way to protect important actions in your application with one time passwords that you provide to users via voice calls. Enjoy!