DEV Community

Cover image for Send One Time Passwords Over Voice Calls in PHP Using Twilio Verify
Yong Dev
Yong Dev

Posted on • Originally published at twilio.com

Send One Time Passwords Over Voice Calls in PHP Using Twilio Verify

In recent years, user authentication in web applications has become a serious concern. For example, one-time passwords (OTP) are utilized to verify a user's identity; the most frequent method for sending OTPs is via SMS to the user's registered cellphone number.

In this article, however, you will learn how to deliver one-time passwords to users via voice calls using Twilio's Verify Api. In doing so, you will create an OTP system that can be used as an additional security layer for specific operations in your application.

Prerequisites

To follow this tutorial, you need the following:

Application Process

Here's how the application will work. Each time a user attempts to register or sign in, a voice call containing a one-time password will be sent to them.
If the user attempts to register, a form to verify the user's phone number is filled out and, if verified, the user is redirected to a form where personal information is collected for storing in the application's database.

When a user attempts to sign in, the data entered in the registration form is compared to the data in the database. If a match is found, an OTP is sent to the user's phone number, and the user is then taken to a page where the OTP is validated. If the OTP is valid, the user is then redirected to the user dashboard.

Create the project directory

Create the project's root directory, called voice-otp, and navigate to it by running the commands below in the terminal.

mkdir voice-otp

cd voice-otp
Enter fullscreen mode Exit fullscreen mode

Create the database

The next step is to create the user table, which will have the following fields:

  • id
  • fullName
  • email
  • password
  • tel (telephone)

In the terminal, run the command below to connect to the MySQL database using MySQL's command-line client, substituting your username for the placeholder ([username]) and enter your password when prompted.

 mysql -u [username] -p
Enter fullscreen mode Exit fullscreen mode

Then, create a database called voice_otp by running the command below.

CREATE DATABASE voice_otp;   
Enter fullscreen mode Exit fullscreen mode

After that, use the command below to verify that the database was created. If it were, you'd see it listed in the terminal output.

SHOW DATABASES;
Enter fullscreen mode Exit fullscreen mode

With the database created, create the users table in the voice_otp database, by running the commands below.

USE voice_otp;
CREATE TABLE user (
  id int auto_increment, 
  fullName varchar(300) NOT NULL,
  email varchar(300) NOT NULL,
  tel varchar(20) NOT NULL,
  password varchar(300) NOT NULL,
  PRIMARY KEY (id) 
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Enter fullscreen mode Exit fullscreen mode

This generates a table with four columns: id (which is auto-increment), fullName, email, tel, and password. Confirm that the table was created by running the SQL statement below.

DESCRIBE user;
Enter fullscreen mode Exit fullscreen mode

With that done, exit MySQL's command-line client by pressing CTRL+D.

Set up the Twilio PHP Helper Library

To utilize Twilio's Verify API for user authentication, you need to install the Twilio PHP Helper Library. To do this, run the command below in your terminal.

composer require twilio/sdk
Enter fullscreen mode Exit fullscreen mode

Then, in your editor or IDE, create a new file named config.php which will contain the app's configuration settings, and paste the code below into the new file.

<?php 

$err_message =  "";

require_once './vendor/autoload.php';

use Twilio\Rest\Client;

$sid = "ACCOUNT_SID";
$token = "AUTH_TOKEN";
$serviceId = "SERVICE_SID";
$twilio = new Client($sid, $token);

Enter fullscreen mode Exit fullscreen mode

The code:

  • Creates a variable for storing errors.
  • Includes Composer's Autoloader file, vendor/autoload.php.
  • Imports Twilio's PHP Helper Library.
  • Creates three variables to store the required Twilio credentials and settings.
  • Initializes a Twilio Client object, which is required to interact with Twilio's Verify API.

Retrieve your Twilio auth token and account SID

The Twilio Console dashboard contains your Twilio Auth Token and Account SID. Copy them and paste them in place of ACCOUNT_SID and AUTH_TOKEN, respectively in config.php.

The next step is to create a Twilio Verify service. First, go to the Twilio Verify Dashboard. Then, click the “Create Service Now” button and give the service a nice name in the "Create New Service" box, as seen in the screenshot below.

Create a new Twilio Verify service with a suitable name, such as "verify".

Create a new Twilio Verify Service

After you've entered a suitable name for the new service, click the blue Create button. The "General Settings" page will appear, which you can see in the screenshot below, where you can view the credentials for your new Twilio Verify service.
Twilio Dashboard
Copy the SERVICE SID value and paste it in place of SERVICE_SID in config.php.

Then, you need to add your database connection details. To do this, first, add the following code to the bottom of config.php.

define('DBHOST', 'localhost'); 
define('DBUSER', 'root');
define('DBPASS', ' ');
define('DBNAME', 'voice_otp');

try {
    $pdo = new PDO("mysql:host=".DBHOST.";dbname=".DBNAME, DBUSER, DBPASS);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch( PDOException $exception ) {
    echo "Connection error :" . $exception->getMessage();
}
Enter fullscreen mode Exit fullscreen mode

Then update the define statements for DBHOST, DBUSER, DBPASS, and DBNAME to match your database's settings. The code uses the four variables and attempts to create a connection to the database.

PDO is used to connect to the database, because it supports several database vendors, including MySQL, MSSQL Server, PostgreSQL, and Oracle.

Next, paste the code below at the end of config.php.


function mask_no($number)
{
    return substr($number, 0, 4) . '************' . substr($number, -4);
}
Enter fullscreen mode Exit fullscreen mode

The function above hides the user's phone number by displaying only the first three and final four digits.

Create the signup and sign in page

Create a new file named signup.php in the project's root directory, and paste the code below into it.

<?php require_once('process.php');

$page = ((empty($_GET['p'])) ? 'step1' : strval($_GET['p']));

if (isset($_SESSION['init']) != true || is_array($_SESSION['init']) != true) {
    $_SESSION['init'] = array();
}

?>

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
    <title>Voice OTP</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" />
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Karla">
    <link rel="stylesheet" href="assets/styles.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.15/css/intlTelInput.css">
    <style>
        .hide {
            display: none;
        }
        #error-msg {
            color: red;
        }
    </style>
</head>

<body style="font-family: Karla, sans-serif;">

    <?php if ($page == 'step1') :  ?>
        <?php require_once('assets/content/step1.phtml'); ?>
    <?php elseif ($page == 'step2' && isset($_SESSION['init']['status']) && !empty($_SESSION['init']['status'])) : ?>
        <?php require_once('assets/content/step2.phtml'); ?>
    <?php else : ?>
        <?php require_once('assets/content/step1.phtml'); ?>

    <?php endif; ?>


    <script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.15/js/intlTelInput.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.15/js/intlTelInput-jquery.min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

    <script>
        var input = document.querySelector("#phone"),
            errorMsg = document.querySelector("#error-msg"),
            validMsg = document.querySelector("#valid-msg");

        // Error messages based on the code returned from getValidationError
        var errorMap = ["Invalid number", "Invalid country code", "Too short", "Too long", "Invalid number"];

        // Initialise plugin
        var intl = window.intlTelInput(input, {
            hiddenInput: "tel",
            initialCountry: "auto",
            autoHideDialCode: true,
            preferredCountries: ['us', 'gb', 'ca', 'ng', 'cn'],
            geoIpLookup: function(success, failure) {
                $.get("https://ipinfo.io", function() {}, "jsonp").always(function(resp) {
                    var countryCode = (resp && resp.country) ? resp.country : "";
                    success(countryCode);
                });
            },
            utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.15/js/utils.js"
        });

        var reset = function() {
            input.classList.remove("error");
            errorMsg.innerHTML = "";
            errorMsg.classList.add("hide");
            validMsg.classList.add("hide");
        };

        // Validate on blur event
        input.addEventListener('blur', function() {
            reset();
            if (input.value.trim()) {
                if (intl.isValidNumber()) {
                    validMsg.classList.remove("hide");
                } else {
                    input.classList.add("error");
                    var errorCode = intl.getValidationError();
                    errorMsg.innerHTML = errorMap[errorCode];
                    errorMsg.classList.remove("hide");
                }
            }
        });

        // Reset on keyup/change event
        input.addEventListener('change', reset);
        input.addEventListener('keyup', reset);
    </script>
</body>

</html>
Enter fullscreen mode Exit fullscreen mode

The code, above, generates a simplistic form for registering new users. It includes the file process.php, which contains the code for processing the sign-in & sign-up forms, connecting to Twilio's Verify API, and preventing users from registering without verifying their phone number.

Next, in the project's root directory, create a new directory named assets, and in that directory, create another directory called content, by running the following commands.

mkdir assets
mkdir assets\content
Enter fullscreen mode Exit fullscreen mode

Then, create two new files, step1.phtml and step2.phtml, in the assets/content directory.

Add the code below to step1.phtml.

<div class="page">
        <section class="register">
            <div class="form-container">
                <form method="POST">
                    <h2 class="text-center"><strong>Create</strong> an account.</h2>
                    <?php if ($err_message) : ?>
                        <div class="callout text-danger">
                            <p><?php echo $err_message; ?></p>
                        </div>
                    <?php endif; ?>

                    <div class="mb-3"><input class="form-control" type="tel" id="phone" name="phone" placeholder="Phone Number" value="<?php echo ((isset($_POST['tel'])) ? $_POST['tel'] : ''); ?>" required></div>
                    <span id="valid-msg" class="hide">✓ Valid</span>
                    <span id="error-msg" class="hide"></span>

                    <div class="mb-3"><button class="btn btn-primary d-block w-100" type="submit" name="telsign" required>Sign Up</button></div><a class="already" href="signin.php">Already have an account? Sign in here.</a>
                </form>
            </div>
        </section>
    </div>
Enter fullscreen mode Exit fullscreen mode

step1.phtml contains code that collects the user’s phone number and sends it to process.php.

International telephone input is used to get all country codes.

Add the code below to step2.phtml.

<div class="page">
        <section class="register">
            <div class="form-container">
                <form method="POST">
                    <h2 class="text-center"><strong>Create</strong> an account.</h2>

                    <?php if ($err_message) : ?>
                        <div class="callout text-danger">
                            <p><?php echo $err_message; ?></p>
                        </div>
                    <?php endif; ?>

                    <div class="mb-3"><input class="form-control" type="text" name="fullName" placeholder="Full Name" value="<?php if (isset($_POST['fullName'])) echo $_POST['fullName']; ?>" ></div>
                    <div class="mb-3"><input class="form-control" type="email" name="email" placeholder="Email" value="<?php if (isset($_POST['email'])) echo $_POST['email']; ?>" ></div>
                    <div class="mb-3"><input class="form-control" type="tel" id="phone" name="phone" placeholder="Phone Number" value="<?php  echo $_SESSION['init']['phone']; ?>"  disabled></div>
                    <div class="mb-3"><input class="form-control" type="password" name="password" placeholder="Password" minlength="8" required></div>
                    <div class="mb-3"><input class="form-control" type="password" name="re_password" placeholder="Password (repeat)" ></div>
                    <div class="mb-3">
                        <div class="form-check"><label class="form-check-label"><input class="form-check-input" type="checkbox"required>I agree to the license terms.</label></div>
                    </div>
                    <div class="mb-3"><button class="btn btn-primary d-block w-100" type="submit" name="signup" required>Sign Up</button></div>
                </form>
            </div>
        </section>
    </div>
Enter fullscreen mode Exit fullscreen mode

Now, create a file named signin.php in the root directory and paste the following code into it:

<?php 
require_once('process.php') 
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
    <title>Sign in-OTP</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"/>
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Karla">
    <link rel="stylesheet" href="assets/styles.css">
</head>

<body style="font-family: Karla, sans-serif;">
    <div class="page">
        <section class="register">
            <div class="form-container">

                <form method="post">
                    <h2 class="text-center"><strong>Sign in</strong> to your account.</h2>
                    <?php if ($err_message) : ?>
                        <div class="callout text-danger">

                            <p><?php echo $err_message; ?></p>
                        </div>
                    <?php endif; ?>
                    <div class="mb-3"><input class="form-control" type="email" name="email" placeholder="Email"></div>
                    <div class="mb-3"><input class="form-control" type="password" name="password" placeholder="Password"></div>
                    <div class="mb-3"><button class="btn btn-primary d-block w-100" type="submit" name="signin">Sign in</button></div><a class="already" href="signup.php">Don't have an account? Sign up here.</a>
                </form>
            </div>
        </section>
    </div>
</body>

</html>
Enter fullscreen mode Exit fullscreen mode

Then, create a file named styles.css in the assets folder and paste the following code in to it:

body {
    background: #f1f7fc;
}

.register {

    padding: 80px 0;
}

.register .form-container {
    display: table;
    max-width: 500px;
    width: 90%;
    margin: 0 auto;
    box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
}

.register form {
    display: table-cell;
    width: 400px;
    background-color: #ffffff;
    padding: 40px 60px;
    color: #505e6c;
}

@media (max-width:991px) {
    .register form {
        padding: 40px;
    }
}

.register form h2 {
    font-size: 24px;
    line-height: 1.5;
    margin-bottom: 30px;
}

.register form .form-control {
    background: #f7f9fc;
    border: none;
    border-bottom: 1px solid #dfe7f1;
    border-radius: 0;
    box-shadow: none;
    outline: none;
    color: inherit;
    text-indent: 6px;
    height: 40px;
}

.register form .form-check {
    font-size: 13px;
    line-height: 20px;
}

.register form .btn-primary {
    background: #f4476b;
    border: none;
    border-radius: 4px;
    padding: 11px;
    box-shadow: none;
    margin-top: 35px;
    text-shadow: none;
    outline: none !important;
}

.register form .btn-primary:hover,
.register form .btn-primary:active {
    background: #eb3b60;
}

.register form .btn-primary:active {
    transform: translateY(1px);
}

.register form .already {
    display: block;
    text-align: center;
    font-size: 12px;
    color: #6f7a85;
    opacity: 0.9;
    text-decoration: none;
}
Enter fullscreen mode Exit fullscreen mode

Following that, create a file named process.php in the project's root directory, and add the following code to it. This code is responsible for processing all requests from the sign in and signup pages.

<?php

//Create Session
require_once("config.php");
ob_start();
session_start();

if (isset($_POST['telsign'])) {
  $phone = $_POST['tel'];

  $statement = $pdo->prepare("SELECT * FROM user WHERE tel=?");
  $statement->execute(array($_POST['tel']));
  $total = $statement->rowCount();
  if ($total) {
    $valid = 0;
    $err_message .= 'An account is already associated with the provided Phone Number<br>';
  }
  else{
  $verification = $twilio->verify->v2->services($serviceId)
    ->verifications
    ->create($phone, "call");
  if ($verification->status) {
    $_SESSION['phone']= $phone;
    header("Location: verify.php?p=signup");

    exit();
  } else {
    echo 'Unable to send verification code';
  }
}
}
if (isset($_POST['signup_verify'])) {
  $code = $_POST['code'];
  $phone = $_SESSION['phone'];

  $verification_check = $twilio->verify->v2->services($serviceId)
    ->verificationChecks
    ->create(
      $code,
      ["to" => "+" . $phone]
    );
  if ($verification_check->status == 'approved') {
    $_SESSION['init']['status'] = 'success';
    $_SESSION['init']['phone'] = $phone;

    header("location: signup.php?p=step2");
  } else {
    $err_message .= 'Invalid code entered<br>';
  }
}

//If the Signup button is triggered
if (isset($_POST['signup'])) {
  $valid = 1;

  /* Validation to check if fullname is inputed */
  if (empty($_POST['fullName'])) {
    $valid = 0;
    $err_message .= " FullName can not be empty<br>";
  }
  //Filtering fullname if not empty
  else {
    $fullname = filter_var($_POST['fullName'], FILTER_SANITIZE_STRING);
  }

  /* Validation to check if email is inputed */
  if (empty($_POST['email'])) {
    $valid = 0;
    $err_message .= 'Email address can not be empty<br>';
  }
  /* Validation to check if email is valid */
  else {
    if (filter_var($_POST['email'], FILTER_VALIDATE_EMAIL) === false) {
      $valid = 0;
      $err_message .= 'Email address must be valid<br>';
    }
    /* Validation to check if email already exist */ else {
      // Prepare our SQL, preparing the SQL statement will prevent SQL injection.
      $statement = $pdo->prepare("SELECT * FROM user WHERE email=?");
      $statement->execute(array($_POST['email']));
      $total = $statement->rowCount();
      //If There is a match gives an error message
      if ($total) {
        $valid = 0;
        $err_message .= 'An account is already asociated with the provided email address<br>';
      }
    }
  }
  /* Validation to check if password is inputed */
  if (empty($_POST['password']) || empty($_POST['re_password'])) {
    $valid = 0;
    $err_message .= "Password can not be empty<br>";
  }
  /* Validation to check if passwords match*/

  if (!empty($_POST['password']) && !empty($_POST['re_password'])) {
    if ($_POST['password'] != $_POST['re_password']) {
      $valid = 0;
      $err_message .= "Passwords do not match<br>";
    }
    //If Passwords Matches Generates Hash
    else {
      //Generating Password hash
      $password = filter_var($_POST['password'], FILTER_SANITIZE_STRING);
      $hashed_password = password_hash($password, PASSWORD_DEFAULT);
    }
  }

  if ($valid == 1) {
    //if There Error Messages are empty Store data in the database
    if (empty($err_message)) {
      //Saving Data Into Database
      $statement = $pdo->prepare("INSERT INTO user (fullName,email,tel,password) VALUES (?,?,?,?)");
      $statement->execute(array($fullname, $_POST['email'], $_POST['tel'], $hashed_password));
      unset($_SESSION['phone']);
      header("location: signin.php");
    } else {
      $err_message .= "Problem in registration.Please Try Again!";
    }
  }
}

if (isset($_POST['signin'])) {
  //Checks if Both input fields are empty
  if (empty($_POST['email'] || empty($_POST['password']))) {
    $err_message .= 'Email and/or Password can not be empty<br>';
  }
  //Checks if email is valid

  else {
    $email = filter_var($_POST['email'], FILTER_VALIDATE_EMAIL);
    if ($email === false) {
      $err_message .= 'Email address must be valid<br>';
    } else {
      //Checks if email exists

      $statement = $pdo->prepare("SELECT * FROM user WHERE email=?");
      $statement->execute(array($email));
      $total = $statement->rowCount();
      $result = $statement->fetchAll(PDO::FETCH_ASSOC);

      if ($total == 0) {
        $err_message .= 'Email Address does not match<br>';
      } else {
        //if email exists save all data in the same column of the email in the row array

        foreach ($result as $row) {
          $stored_password = $row['password'];
        }
      }
    }
    //Checks Provided password matches the password in database if it does logs user in
    if (password_verify($_POST['password'], $stored_password)) {
      //setting the session variables
      $_SESSION['user'] = $row;
      $_SESSION['user']['status'] = '';
      $_SESSION['phone'] = $row['tel'];
      //setting the session signin time
      $_SESSION['user']['loggedin_time'] = time();
      $verification = $twilio->verify->v2->services($serviceId)
        ->verifications
        ->create($_SESSION['phone'], "sms");
      if ($verification->status) {

        header("Location: verify.php?p=signin");

        exit();
      }
    } else {
      $err_message .= 'Password does not match<br>';
    }
  }
}
if (isset($_POST['signin_verify'])) {
  $code = $_POST['code'];
  $phone = $_SESSION['phone'];

  $verification_check = $twilio->verify->v2->services($serviceId)
    ->verificationChecks
    ->create(
      $code,
      ["to" => "+" . $phone]
    );
  if ($verification_check->status == 'approved') {
  $_SESSION['user']['status'] = 'success';

    header("Location: index.php");

    //destroy created session

  } else {
    $err_message .= 'Invalid code entered<br>';
  }
}
Enter fullscreen mode Exit fullscreen mode

Stepping through the code, first, config.php, which contains the app's configuration settings, is included. Then, a session is created with a condition that checks if the provided number is not registered in the database. It then sends a verification code to the user, who is then directed to a page that verifies the authenticity of the code. If it is, then a session that allows the user to access the step2.phtml file, which collects the data which would be stored in the database, is created.

The next line verifies the form input and saves it to a database. If a user signs in successfully, they are routed to the verification page, and if authorized a session named user and a sub-array called status are initialized to success, allowing the user to access index.php, which is the dashboard.

Now, create three files in the root directory:

  • index.php for the dashboard
  • verify.php for inputting the verification code
  • logout.php which signs out a user from the existing session

After they're created, paste the following code into index.php:

<?php
session_start();
//require_onces database config
require_once("config.php");
// Check if the user is logged in or not
if (!isset($_SESSION['user'])) {
    header('location: signin.php');
    exit;
}
// Check if the user has verified number or not
if ($_SESSION['user']['status']!=='success') {
    header('location: signin.php');
    exit;
}
?>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
    <title>DashBoard</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"/>
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Karla">
    <link rel="stylesheet" href="assets/styles.css">
</head>

<body style="font-family: Karla, sans-serif;">
    <div class="page">
        <section class="register">
            <div class="form-container">

                <h2 class="text-center"><strong>User </strong> DashBoard.</h2>


                <div class="callout text-primary">
                    Welcome <?php echo $_SESSION['user']['fullName']; ?>. Click here to <a href="logout.php" tite="Logout">Logout.

                </div>


            </div>
        </section>
    </div>
    </body>

</html>
Enter fullscreen mode Exit fullscreen mode

Now, start PHP's built-in web server by running the following command:

php -S localhost:8000 
Enter fullscreen mode Exit fullscreen mode

Then, open http://localhost:8000/ in your browser, the sign in screen should look like:

Sign in form.

In verify.php, paste the code below.

<?php require_once('process.php');


$page = ((empty($_GET['p'])) ? '' : strval($_GET['p']));
$number = $_SESSION['phone'];
?>
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
    <title>Voice OTP</title>
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Karla">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"/>
    <link rel="stylesheet" href="assets/styles.css">
</head>

<body style="font-family: Karla, sans-serif;">
    <div class="page">
    <?php if($page=='signup' && isset($_SESSION['phone'])):  ?>
        <section class="register">
            <div class="form-container">

                <form method="post">
                    <h2 class="text-center"><strong>Verify</strong> your account.</h2>

                    </p>A verification code has been sent through Voice call to
                    <b><?php echo mask_no($number); ?></b>, <br />
                    enter the code below to continue.</p>
                    <?php if ($err_message) : ?>
                        <div class="callout text-danger">

                            <p><?php echo $err_message; ?></p>
                        </div>
                    <?php endif; ?>
                    <div class="mb-3"><input class="form-control" type="text" name="code" placeholder="Code"></div>

                    <div class="mb-3"><button class="btn btn-primary d-block w-100" type="submit" name="signup_verify">Verify</button></div><a class="already" href="signup.php">Wrong Number?</a>
                </form>
            </div>
        </section>
        <?php elseif($page=='signin'  && isset($_SESSION['phone'])):?>
        <section class="register">
            <div class="form-container">

                <form method="post">
                    <h2 class="text-center"><strong>Verify</strong> your account.</h2>

                    </p>A verification code has been sent through Voice call to
                    <b><?php echo mask_no($number); ?></b>, <br />
                    enter the code below to continue.</p>
                    <?php if ($err_message) : ?>
                        <div class="callout text-danger">

                            <p><?php echo $err_message; ?></p>
                        </div>
                    <?php endif; ?>
                    <div class="mb-3"><input class="form-control" type="text" name="code" placeholder="Code"></div>

                    <div class="mb-3"><button class="btn btn-primary d-block w-100" type="submit" name="signin_verify">Verify</button></div>
                </form>
            </div>
        </section>
            <?php else: ?>

                        <?php header('HTTP/1.0 403 Forbidden'); echo 'Error Cannot access resource'; ?>

                    <?php endif; ?>
    </div>
</body>

</html>
Enter fullscreen mode Exit fullscreen mode

The Verification screen:

Verify your account form.

Parameters were set in place to prevent a user from accessing the verify.php page, if they haven't gone through the verification process.
And in logout.php, paste the code below.

<?php
ob_start();
session_start();
//includes app  config file
require_once('config.php');
//Unset the session variable
unset($_SESSION['user']);
//destroy created session
session_destroy();
// Redirect to login page
header("location: signin.php");
Enter fullscreen mode Exit fullscreen mode

Note

If you are making calls from a trial account, the "To" phone number must be verified with Twilio. You can verify your phone number by adding it to your Verified Caller IDs in the console.

That's how to use Twilio Verify to deliver an OTP via calls with PHP.

There you have it, a mechanism to encrypt crucial activities in your app using one-time passwords sent via phone calls to users. In this tutorial, you learned how to send an OTP via a phone call with Twilio verify.
However, Verify has a great deal more functionality than has been covered in this tutorial. Check out Twilio’s Verify docs if you're keen to learn more about the available functionality. For your convenience, the whole code for this article is accessible on Github.

If you enjoyed this piece you can follow me on twitter @ _yongdev

Top comments (5)

Collapse
 
ravavyr profile image
Ravavyr

Nicely done. It's rare to see anyone writing a thorough PHP tutorial like this nowadays.
You covered a lot of bases and did it well.

I'm not a fan of composer so i would've just grabbed just the necessary code form their repo or something, but other than that, really enjoyed reading this one.

I was expecting more like just the PHP code to make an API call Twilio, and you did the entire interface setup which is very nice.

Collapse
 
yongdev profile image
Yong Dev

Thank you.

Since you aren't a fan of composer you can grab the sdk source from their repo, and replace "require_once './vendor/autoload.php';" with "require DIR . '/twilio-php-main/src/Twilio/autoload.php';"

Collapse
 
techman09 profile image
TechMan09

Cool. How may automated calls are we allowed to make on the free plan? Or how much can this be used before you get charged?

Collapse
 
yongdev profile image
Yong Dev

You can check their pricing page here

Collapse
 
yongdev profile image
Yong Dev

Got a question?