Key Takeaways
- Multi-factor authentication, requiring verification from at least two different approaches, is considerably more secure than traditional single-factor authentication. One practical approach to multi-factor authentication is requiring a password and a confirmation token sent to the user via phone either by SMS or voice call.
- Twilio offers the infrastructure and an API which developers can use to write interactive telephony applications, including multi-factor authentication systems. Developers can make and receive phone calls and send and receive text messages with Twilio using TwiML (Twilio Markup Language) and their REST API.
- Implementing multi-factor authentication with Twilio involves creating a new instance of the Services_Twilio class, accessing their API through the account property, and sending an SMS message or initiating a voice call through the create() method of an SMS message instance resource or Calls instance resource respectively.
- The multi-factor authentication process involves presenting the user with a login form which initiates the process, verifying her credentials, sending a code to her phone, and only authorizing the user after she submits the correct code. This process can be customized and enhanced with features such as a link to resend the confirmation code, account locking if an incorrect code has been provided, and logging of authentication events.
Using Twilio from PHP
Connecting to the Twilio service is as easy as including the twilio-php library and creating a new instance of theServices_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 thecreate()
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 thecreate()
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!)
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 FotoliaFrequently 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.
Timothy Boronczyk is a native of Syracuse, New York, where he lives with no wife and no cats. He has a degree in Software Application Programming, is a Zend Certified Engineer, and a Certified Scrum Master. By day, Timothy works as a developer at ShoreGroup, Inc. By night, he freelances as a writer and editor. Timothy enjoys spending what little spare time he has left visiting friends, dabbling with Esperanto, and sleeping with his feet off the end of his bed.