Multi-Factor Authentication with PHP and Twilio

Timothy Boronczyk
Timothy Boronczyk
Share

There are various approaches used to confirm people are in fact who they say they: reference some aspect of the user herself (e.g. biometrics), ask for something the user knows (e.g. a password), or ask for something the user physically has (e.g. an RFID card). Traditional websites implement single-factor authentication, requiring just a user’s password. But multi-factor authentication on the other hand requires verification from at least two of the different approaches and is considerably more secure. Using biometrics to authenticate someone to your website is probably still a long ways off with many technological hurdles and civil liberties concerns to overcome. A more practical approach to multi-factor authentication is to implement options from the other two categories, such as requiring a password and a confirmation token sent to the user via phone either by SMS or voice call (the user knows the password, and has a phone). This is what you’ll learn how to implement in this article. Telephony integration can be frustrating if you’re doing it from the ground up, and most of the time it’s not practical to build up your own infrastructure (though possible with commodity hardware and programs like Asterisk). Luckily, Twilio offers the infrastructure and an API which developers can use to write interactive telephony applications without much hassle. I’ll be making use of their services in this article. You can make and receive phone calls and send and receive text messages with Twilio using TwiML (Twilio Markup Language) and their REST API. You can work directly with the API, or use one of the available helper libraries. The library I’ll use here is twilio-php, the library released and officially supported by Twilio. So, are you ready to learn how you can implement multi-factor authentication? Read on!

Using Twilio from PHP

Connecting to the Twilio service is as easy as including the twilio-php library and creating a new instance of the Services_Twilio class. The object’s constructor accepts your account’s SID and auth token which are listed on your Twilio account’s dashboard page after you sign up for their service. With an available Services_Twilio instance at your disposal, you can access their API though the account property. account exposes an instance of Services_Twilio_Rest_Account which represents your Twilio account. I’m only using one account here, but it is possible to have multiple sub-accounts. This can be useful for segmenting your interactions depending on your needs. You can learn more about sub-accounts
by reading the Twilio documentation.
<?php
require "Services/Twilio.php";

define("TWILIO_SID", "…");
define("TWILIO_AUTH_TOKEN", "…");

$twilio = new Services_Twilio(TWILIO_SID, TWILIO_AUTH_TOKEN);
$acct = $twilio->account;
The account instance exposes several other properties, among which are calls and sms_messages. These are instances of objects like Services_Twilio_Rest_Calls and Services_Twilio_Rest_SmsMessages which encapsulate the REST resources used to issue your calls and messages respectively. Yet, you rarely work with these objects beyond their create() methods, and the documentation refers to the properties that expose them as “instance resources.” I’ll do the same to help avoid any confusion.

Sending an SMS Message

Sending an SMS message is done through the create() method of an SMS message instance resource ($acct->sms_messages). The method takes three arguments: the Twilio number of your account (akin to the “from address” of an email), the recipient’s number (the “to address”), and the text of your message (which can be up to 160 characters).
<?php
$code = "1234"; // some random auth code

$msg = $acct->sms_messages->create(
    "+19585550199", // "from" number
    "+19588675309", // "to" number
    "Your access code is " . $code
);
Behinds the scenes, the twilio-php library issues a POST request of some TwiML on your behalf to the Twilio API. An instance of Services_Twilio_Rest_SmsMessage is returned by the call, which encapsulates information about the message. You can see a full list of what information is made available in the documentation
, but probably the more important values are exposed by the status and price properties. status reveals the status of the SMS message (either queued, sending, sent, or failed), and price reveals the amount billed to your account for the message.

Sending a Voice Call

Initiating a voice call is done through the create() method of a Calls instance resource ($acct->calls). Like sending SMS messages, you need to provide your account number, the recipient’s number, and the message. In this case, however, the message is a URL to a TwiML document that describes the nature of the call.
<?php
// Note spaces between each letter in auth code. This forces
// the speech synthesizer to enunciate each digit.
$code = "1 2 3 4";

$msg = $acct->calls->create(
    "+19585550199", // "from" number
    "+19588675309", // "to" number
    "http://example.com/voiceCall.php?code=" . urlencode($code)
);
Again the library issues a POST request on your behalf and a voice call is made. When the callee answers her telephone, Twilio’s processes retrieve and execute the commands provided in the callback URL’s XML. In the above sample that initiates a voice call, I’ve passed the confirmation code as a GET parameter in the URL to the callback script. When the URL is retrieved by Twilio, PHP will use the parameter when rendering the response. There are only a handful of TwiML tags you need to construct the call flow, but you can use them to define flows that are quite complex (such as phone tree menus, etc.). A basic call flow for this type of scenario though could be generated by PHP and look something like this:
<?php
header("Content-Type: text/xml");
$code = isset($_GET["code"]) ? htmlspecialchars($_GET["code"]) : null;
$digit = isset($_POST["Digits"]) ? (int)$_POST["Digits"] : null;
$url = htmlspecialchars($_SERVER["PHP_SELF"]) . "?code=" . $code;

echo '<?xml version="1.0" encoding="UTF-8"?>';
?>
<Response>
<?php
if (is_null($code)) {
?>
 <Say>Sorry. An error occurred.</Say>
<?php
}
else {
?>
 <Gather action="<?php echo $url; ?>" numDigits="1">
<?php
    if (is_null($digit) || $digit == 1) {
?>
  <Say>Your access code is <?php echo $code; ?></Say>
<?php
    }
?>
  <Say>Press 1 to repeat the code.</Say>
 </Gather>
<?php
}
?>
 <Say>Good bye.</Say>
</Response>
The TwiML tags used here are Response
(the root element), Say (provides text that will be spoken by Twilio), and Gather (collects input from the user). While speaking the text as indicated by the child Say elements, Twilio will also be listening for user input because of Gather, pausing five seconds afterwards to provide a window for the user to enter her response. If Gather
times out without input, it exits and executes the subsequent Say text and terminates the call. Otherwise, the input is POSTed back to the action URL for further handling. The Twilio docs for Gather are very good at explaining the behavior and the element attributes you can use to modify the behavior, and even lists a couple samples. I recommend you give it a quick read. There’s one last thing of note before moving on; I’ve added spaces between each digit in the auth code in the initiating script. This forces the speech synthesizer to enunciate each digit saying “one two three four” instead of “one thousand two hundred thirty four.” With speech synthesis, sometimes what we see in text isn’t always what we get. It’s okay to fudge or misspell voice dialog if it results in better clarity and understanding for your callees.

Implementing Multi-Factor Authentication

Now that you understand the basic workflow for SMS and voice interactions with Twilio, it’s time to see where it fits into the login process. What you’ll see here is pretty straight forward, and I’ve reduced the incidental code for the sake of clarity since the particulars of your own login process will inevitably vary. The user should be presented with a login form which initiates the process. The form submission leads you to verifying her credentials and rejecting them if they’re bad, but valid credentials shouldn’t immediately authenticate the user to your application. Instead, consider the user “partially authenticated”, a state which allows her to view a second form requesting the code which has been sent to her phone. Only after she submits the correct code should the user be authorized.
<?php
session_start();

require "Services/Twilio.php";

define("TWILIO_SID", "…");
define("TWILIO_AUTH_TOKEN", "…");
define("TWILIO_FROM_NUMBER", "+19585550199");
define("TWILIO_VOICE_URL", "http://example.com/voiceCall.php");

define("DB_HOST", "localhost");
define("DB_DBNAME", "test");
define("DB_USER", "dbuser");
define("DB_PASSWORD", "dbpassword");

define("CRYPT_SALT", '$2a$07$R.gJb2U2N.FmZ4hPp1y2CN');

define("AUTH_CODE_LENGTH", 4);

$db = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_DBNAME,
    DB_USER, DB_PASSWORD);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

if ($_SERVER["REQUEST_METHOD"] == "POST") {
    // first stage of authentication
    if (empty($_SESSION["username"])) {
        $username = isset($_POST["username"])
            ? $_POST["username"] : "";
        $password = isset($_POST["password"])
            ? crypt($_POST["password"], CRYPT_SALT) : "";

        $query = sprintf("SELECT username, phone_number, code_pref FROM users WHERE username = %s AND password = %s",
           $db->quote($username),
           $db->quote($password));
        $result = $db->query($query);
        $row = $result->fetch(PDO::FETCH_ASSOC);
        $result->closeCursor();

        // invalid username/password provided
        if (!$row) {
            session_unset();
        }
        // valid username/password
        else {
            $_SESSION["isAuthenticated"] = false;
            $_SESSION["username"] = $row["username"];

            // generate and send auth code
            $code = "";
            for ($i = 0; $i < AUTH_CODE_LENGTH; $i++) {
                $code .= rand(0, 9);
            }

            $twilio = new Services_Twilio(TWILIO_SID, TWILIO_AUTH_TOKEN);
            $acct = $twilio->account;

            // send code via voice or SMS depending on
            // the user's preference
            if ($row["code_pref"] == "voice") {
                // add spaces to force enunciation
                $tmpCode = join(" ", string_split($code));
                $msg = $acct->calls->create(
                    TWILIO_FROM_NUMBER,
                    $_row["phone_number"],
                    TWILIO_VOICE_URL . "?code=" .
                        urlencode($tmpCode)
                );
            }
            else {
                $msg = $acct->sms_messages->create(
                    TWILIO_FROM_NUMBER,
                    $_row["phone_number"],
                    "Your access code is " . $code
                );
            }

            // "remember" code in session
            $_SESSION["code"] = $code;
        }
    }
    // second stage authentication
    else {
        $code = isset($_POST["code"]) ? $_POST["code"] : "";
        if ($code == $_SESSION["code"]) {
            $_SESSION["isAuthenticated"] = true;
            unset($_SESSION["code"]);
        }
    }
}

if (!empty($_SESSION["isAuthenticated"])) {
?>
<h1>W00t! You're Authenticated!</h1>
<?php
}
// present login forms
else {
    if (empty($_SESSION["username"])) {
?>
<h1>Login Step 1</h1>
<form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
 <input type="text" name="username">
 <input type="password" name="password">
 <input type="submit" value="Login">
</form>
<?php
    }
    else {
?>
<h1>Login Step 2</h1>
<form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
 <input type="text" name="code">
 <input type="submit" value="Confirm">
</form>
<?php
    }
}
The above code is only meant to be illustrative. In your real-world application, you might want to consider adding the following:
  • Add a link to resend the confirmation code to the user’s phone.
  • Add a cancel link on the code request form in case the user decides not to continue with the process. With respect to the code above, such a link would need to unset $_SESSION["username"] since the value, besides storing the username, also acts as a “partial authentication” flag with respect to $_SESSION["isAuthenticated"].
  • Add throttling or account locking if an incorrect code has been provided.
  • Depending on your level of paranoia or the compliance requirements you face, log the authentication events somewhere. (An application which requires multi-factor authentication is usually sensitive enough to warrant an audit trail!)
Additionally, you may want to put some thought into creating sufficiently complex authentication codes for your purposes. 4-digit numeric codes are pretty common, but you’re not limited to that. If you choose to use letters or a mix of alphanumeric values, I’d suggest avoiding values that can be easily confused (such as the number 0 vs the letter O, the number 1 vs the letter I, etc.).

Summary

The proliferation of affordable mobile devices and IP-Telephony has added additional channels for interacting with users, and in this article you learn one way to leverage these channels by implementing multi-factor authentication using the “cloud communications” service Twilio. I hope you’ve found this article helpful in understanding both multi-factor authentication and Twilio. Feel free to leave a comment below sharing how you’ve used Twilio or implemented multi-factor authentication in your own applications. I’d love to hear about it! Image via Fotolia

Frequently Asked Questions (FAQs) about Multi-Factor Authentication with PHP and Twilio

How secure is multi-factor authentication with PHP and Twilio?

Multi-factor authentication (MFA) with PHP and Twilio is highly secure. It adds an extra layer of security by requiring users to provide at least two forms of identification before they can access their account. The first form is usually a password, and the second can be a code sent to the user’s phone via SMS. This makes it much harder for unauthorized users to gain access to accounts, even if they have the password.

Can I customize the authentication process?

Yes, you can customize the authentication process to suit your needs. Twilio’s API is flexible and allows you to tailor the authentication process to your specific requirements. You can choose the type of second factor (like SMS, voice call, or email), the message content, and more.

How reliable is Twilio for sending authentication codes?

Twilio is a reliable platform for sending authentication codes. It has a robust infrastructure and provides high deliverability rates. It also offers real-time delivery reports, so you can track the status of your messages.

What happens if a user loses their phone?

If a user loses their phone, they can still access their account using backup codes. These are generated at the time of setting up MFA and should be stored in a safe place. Users can enter these codes instead of the ones sent to their phone.

Can I use MFA with PHP and Twilio for large-scale applications?

Yes, you can use MFA with PHP and Twilio for large-scale applications. Twilio’s infrastructure is designed to handle high volumes of messages, making it suitable for applications with a large number of users.

How can I test the MFA implementation?

You can test the MFA implementation by creating a test account and going through the authentication process. Twilio also provides a sandbox environment where you can test your code without sending actual messages.

Is it possible to implement MFA without coding?

While it’s possible to use some MFA solutions without coding, implementing MFA with PHP and Twilio requires some coding knowledge. However, the process is straightforward, and there are many resources available to help you.

How much does it cost to use Twilio for MFA?

The cost of using Twilio for MFA depends on the number of messages you send. Twilio charges per message, so the more messages you send, the higher the cost. However, Twilio offers competitive pricing and volume discounts.

Can I use MFA with PHP and Twilio for non-web applications?

Yes, you can use MFA with PHP and Twilio for non-web applications. You can implement MFA in any application that can send HTTP requests and handle HTTP responses.

What other services does Twilio offer?

In addition to MFA, Twilio offers a range of communication services, including voice, video, chat, and email. These services can be used to build a variety of applications, from customer service systems to marketing automation tools.