Send One Time Passwords Over Voice Calls in PHP Using Twilio Verify
Tags: twilio, php, webdev, tutorial
Published: 2022-08-01 18:30:00

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](https://www.twilio.com/docs/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:

- PHP 7.4 or higher with the PDO extension [and PDO MySQL extension installed and enabled](https://www.php.net/manual/en/pdo.installation.php).
- [Composer](https://getcomposer.org/) globally installed.
- [MySQL](https://www.mysql.com/) and [the MySQL command-line client](https://dev.mysql.com/doc/refman/8.0/en/mysql.html) (or an alternative database tool, such as [DataGrip](https://www.jetbrains.com/datagrip/)).
- A Twilio account. If you are new to [Twilio](http://www.twilio.com/referral/v0KQ88), click here to create a free account.

## 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.

```bash
mkdir voice-otp

cd voice-otp
```

## 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.


```bash
 mysql -u [username] -p
```


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

```sql
CREATE DATABASE voice_otp;   
```

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.

```sql
SHOW DATABASES;
```
With the database created, create the users table in the `voice_otp` database, by running the commands below.

```sql
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;
```
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.

```sql
DESCRIBE user;
```
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](https://www.twilio.com/docs/libraries/php). To do this, run the command below in your terminal.

```bash
composer require twilio/sdk
```

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
<?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);

```

The code:

- Creates a variable for storing errors.
- Includes Composer's Autoloader file, _vendor/autoload.php_.
- Imports [Twilio's PHP Helper Library](https://www.twilio.com/docs/libraries/php).
- 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3dbb3n64flevjri5bq3i.png)


The [Twilio Console dashboard](https://console.twilio.com/?frameUrl=/console) 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](https://console.twilio.com/us1/develop/verify?frameUrl=%2Fconsole%2Fverify%3Fx-target-region%3Dus1&currentFrameUrl=%2Fconsole%2Fverify%2Fdashboard%3F__override_layout__%3Dembed%26bifrost%3Dtrue%26x-target-region%3Dus1). 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ox4uejynywexv8b3zkkp.png)


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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2e5r2w8qtodyxhwhjxby.png)
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_.

```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();
}
```

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_.

```php

function mask_no($number)
{
    return substr($number, 0, 4) . '************' . substr($number, -4);
}
```

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
<?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>
```

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.

```bash
mkdir assets
mkdir assets\content
```
Then, create two new files, _step1.phtml_ and _step2.phtml_, in the _assets/content_ directory.


Add the code below to _step1.phtml_.
```php
<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>
```
_step1.phtml_ contains code that collects the user’s phone number and sends it to _process.php_. 


> [International telephone input](https://www.twilio.com/blog/international-telephone-input-twilio) is used to get all country codes. 

Add the code below to _step2.phtml_.

```php
<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>
```
Now, create a file named _signin.php_ in the root directory and paste the following code into it:


```php
<?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>
```
Then, create a file named _styles.css_ in the assets folder and paste the following code in to it:
```css
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;
}
```
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
<?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>';
  }
}
```

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
<?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>
```

Now, start PHP's built-in web server by running the following command:
```bash
php -S localhost:8000 
```
Then, open [http://localhost:8000/](http://localhost:8000/) in your browser, the sign in screen should look like:

![Sign in form.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nz60lj0bzfop3stb5oay.png)
 
In _verify.php_, paste the code below.

```php
<?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>
```
The Verification screen:

![Verify your account form.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kh0yw13qqmbn59redoqt.png)
 
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
<?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");
```

>#### Note
> If you are making calls from a trial account, the "To" phone number must be [verified with Twilio](https://www.twilio.com/docs/usage/tutorials/how-to-use-your-free-trial-account#verify-your-personal-phone-number). You can verify your phone number by adding it to your [Verified Caller IDs](https://www.twilio.com/console/phone-numbers/verified) 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](https://www.twilio.com/docs/verify/quickstarts/php-laravel) if you're keen to learn more about the available functionality. For your convenience, the whole code for this article is accessible on [Github](https://github.com/prosperyong/Twilio-OTP-System/).


If you enjoyed this piece you can follow me on twitter @<a href="https://twitter.com/_yongdev?ref_src=twsrc%5Etfw" class="twitter-follow-button" data-show-count="false"> _yongdev</a>