Get Started

Two-Factor Authentication

Two-Factor Authentication is a more secure way of logging in to a website. In addition to entering a password online, a user has to enter a random verification code generated at login time. This combination of passwords makes it easier to safeguard your applications.

Historically companies that want to implement two-factor authentication distribute little devices to all of their employees that generate passcodes on demand. But these are expensive and get lost easily. With Twilio you can set up your two-factor authentication system to run on a devices all of your employees already carry with them - their cellphone. Companies such as Intuit have built their security validation system around an app powered by Twilio.

Usage

There are three steps involved in building a two-factor authentication system.

  1. We want to collect the username, phone number, and the user's preferred method of contact.

  2. Next, we want to generate and send that password via a second (non-email/web) channel that an attacker is unlikely to have.

  3. Finally, compare our originally generated password against the submitted password.

This HowTo is written in PHP without the use of a framework or any libraries other than our PHP Helper Library. The code should work on on PHP 5.2+.

Concepts

This HowTo shows how to send a simple message via SMS or an outgoing call using the Twilio REST API and the usage of the TwiML <Say> verb.

Download

two-factor-authentication.zip

Implementation

  • 1

    • Our application displays a simple login page offering a username box and a preferred contact method.

      • howtos/two-factor-authentication/index.php
        <?php
        
        session_start();
        
        ?>
        <html>
            <head>
                <title>Two Factor Authentication Demo</title>
                <style>
                    .center {
                        margin-left: auto;
                        margin-right: auto;
                        margin-top: 25px;
                    }
        
                    #submit { float: right; }
        
                    form { border-style: solid; padding: 10px; width: 300px; }
        
                    input[type="button"], input[type="text"], input[type="password"]
                        { float: right; }
        
                    div { text-align: center; width: 500px; }
                </style>
            </head>
            <body>
                <div class="center">
                    <p>This is just a demo that demonstrates how voice/SMS could be
                        integrated to build a simple two-factor authentication system
                        for better security and fraud prevention.</p>
        
                    <p>No matter what username you put into the initial box, the system
                        will generate a one-time use password similar to an RSA token.
                        Once this password is used, the user's session is set and the
                        password is destroyed. In this particular case, we're not
                        storing anything long term.</p>
        
                    <span id="message">
                        <?php
                        $message = urldecode($_GET['message']);
                        echo preg_replace("/[^A-Za-z0-9 ,']/", "", $message);
                        $action = (isset($_SESSION['password'])) ? 'login' : 'token';
                        ?>
                    </span>
                </div>
                <form id="reset-form" action="process.php" method="POST" class="center">
                    <input type="hidden" name="action" value="<?php echo $action; ?>" />
                    <p>Username: <input type="text" name="username" id="username" value="<?php echo $_SESSION['username']; ?>" /></p>
        
                    <?php if (isset($_SESSION['password'])) { ?>
                        <p>Password: <input type="password" name="password" id="password" /></p>
                    <?php } else { ?>
                        <p>Phone Number: <input type="text" name="phone_number" id="phone_number" /></p>
                        Preferred method:<br />
                        SMS: <input type="radio" name="method" value="sms" checked="checked" />
                        Voice: <input type="radio" name="method" value="voice" />
                    <?php } ?>
        
                    <p><input type="submit" name="submit" id="submit" value="login!" /></p>
                    <p>&nbsp;</p>
                </form>
            </body>
        </html>    

      The user enters their username, selects their preferred contact method, and clicks "token!" Upon submission, the user's browser requests process.php which determines the required action and calls the appropriate function within functions.php

      • howtos/two-factor-authentication/process.php
        <?php
        
        include 'functions.php';
        
        /*
         * First we retrieve each of the relevant variables and remove any
         *   non-alphanumeric characters filter them to protect against things such
         *   as SQL Injection.
         */
        $username = isset($_POST['username']) ? $_POST['username'] : '';
        $username = preg_replace("/[^A-Za-z0-9]/", "", $username);
        $password = isset($_POST['password']) ? $_POST['password'] : '';
        $password = preg_replace("/[^A-Za-z0-9]/", "", $password);
        $phoneNum = isset($_POST['phone_number']) ? $_POST['phone_number'] : '';
        $phoneNum = preg_replace("/[^0-9]/", "", $phoneNum);
        $method   = isset($_POST['method']) ? $_POST['method'] : '';
        
        $action   = isset($_POST['action']) ? $_POST['action'] : '';
        switch ($action) {
            case 'token':
                $message = user_generate_token($username, $phoneNum, $method);
                break;
            case 'login':
                $message = user_login($username, $password);
                break;
            default:
                echo 'do nothing';
        }
        header("Location: index.php?message=" . urlencode($message));    
  • 2

    • Within the function user_generate_token, the script generates a random 10 character password and stores it in the session. It then uses the specified contact method to send the password to the user. If the method is SMS, a REST request is generated to send the SMS. Alternatively, if Voice is selected, the function inserts spaces and commas into the password to slow down the Text to Speech engine, combines it with one our Twimlets, and calls the user. Within a few seconds, they receive the password as expected.

      • howtos/two-factor-authentication/functions.php
        <?php
        session_start();
        
        include 'Services/Twilio.php';
        
        /*
         * This file simply includes our Account SID, Auth Token, and a Twilio phone
         * number denoted below as $fromNumber.
         */
        include 'credentials.php';
        
        /*
         * This function takes a username and a preferred contact method, generates a
         *   new password, and sends it to the user via their preferred contact method.
         *
         * This is the most complicated of all the functions because it has so many
         *   steps. If you look at each piece individually, none are complicated.
         */
        function user_generate_token($username, $phoneNum, $method){
            global $accountsid, $authtoken, $fromNumber;
        
            // Create a new password
            $password = substr(md5(time().rand(0, 10^10)), 0, 10);
            // Store the username and password.
            $_SESSION['username'] = $username;
            $_SESSION['password'] = $password;
        
            $client = new Services_Twilio($accountsid, $authtoken);
            // Prepare the message with the password embedded
            $content = ('sms' == $method) ? "Your newly generated password is ".$password :
                "http://twimlets.com/message?Message%5B0%5D=Your%20newly%20generated%20password%20is%20%2C%2C" .
                urlencode(preg_replace("/(.)/i", "\${1},,", $password)) .
                "%20To%20repeat%20that%2C%20your%20password%20is%20%2C%2C" . urlencode(preg_replace("/(.)/i", "\${1},,", $password));
            $method  = ('sms' == $method) ? 'sms_messages' : 'calls';
        
            // Send the message via SMS or Voice
            $item = $client->account->$method->create(
                        $fromNumber,    // The Twilio number we're sending from
                        $phoneNum,      // The user's phone number
                        $content
                    );
            $message = "A new password has been generated and sent to your phone number.";
        
            return $message;
        }
        
        function user_login($username, $submitted) {
        
            // Retrieve the stored password
            $stored = $_SESSION['password'];
            // Compare the retrieved vs the stored password
            if ($stored == $submitted) {
                $message = "Hello and welcome back $username";
            } else {
                $message = "Sorry, that's an invalid username and password combination.";
            }
            // Clean up after ourselves
            unset($_SESSION['username']);
            unset($_SESSION['password']);
        
            return $message;
        }    
  • 3

    • On the web side of the application, the user is forwarded to a simple login page requiring a username and password.
  • 4

    • When the user inputs their username and the password from the message, the application uses the user_login function to check the submitted information against the previously submitted username and the previously generated password.

      • howtos/two-factor-authentication/functions.php
        <?php
        session_start();
        
        include 'Services/Twilio.php';
        
        /*
         * This file simply includes our Account SID, Auth Token, and a Twilio phone
         * number denoted below as $fromNumber.
         */
        include 'credentials.php';
        
        /*
         * This function takes a username and a preferred contact method, generates a
         *   new password, and sends it to the user via their preferred contact method.
         *
         * This is the most complicated of all the functions because it has so many
         *   steps. If you look at each piece individually, none are complicated.
         */
        function user_generate_token($username, $phoneNum, $method){
            global $accountsid, $authtoken, $fromNumber;
        
            // Create a new password
            $password = substr(md5(time().rand(0, 10^10)), 0, 10);
            // Store the username and password.
            $_SESSION['username'] = $username;
            $_SESSION['password'] = $password;
        
            $client = new Services_Twilio($accountsid, $authtoken);
            // Prepare the message with the password embedded
            $content = ('sms' == $method) ? "Your newly generated password is ".$password :
                "http://twimlets.com/message?Message%5B0%5D=Your%20newly%20generated%20password%20is%20%2C%2C" .
                urlencode(preg_replace("/(.)/i", "\${1},,", $password)) .
                "%20To%20repeat%20that%2C%20your%20password%20is%20%2C%2C" . urlencode(preg_replace("/(.)/i", "\${1},,", $password));
            $method  = ('sms' == $method) ? 'sms_messages' : 'calls';
        
            // Send the message via SMS or Voice
            $item = $client->account->$method->create(
                        $fromNumber,    // The Twilio number we're sending from
                        $phoneNum,      // The user's phone number
                        $content
                    );
            $message = "A new password has been generated and sent to your phone number.";
        
            return $message;
        }
        
        function user_login($username, $submitted) {
        
            // Retrieve the stored password
            $stored = $_SESSION['password'];
            // Compare the retrieved vs the stored password
            if ($stored == $submitted) {
                $message = "Hello and welcome back $username";
            } else {
                $message = "Sorry, that's an invalid username and password combination.";
            }
            // Clean up after ourselves
            unset($_SESSION['username']);
            unset($_SESSION['password']);
        
            return $message;
        }