Almost every Web developer has built a login page, and probably more than one. To show how to protect a login with phone number powered second factor authentication (2FA), we'll walk through the process of adding Nexmo's Verify API to an existing Web application.
Our Web application is a simple todo list. All the application data, and even the login, is powered by Parse. That will allow us to focus on the code that powers 2FA and abstract away the code needed to persist todo items and store passwords. It also means running this example app yourself only requires PHP and a Parse account.
If you're comfortable with modern PHP frameworks, you should be able to follow this how-to in your own development environment. Once you clone the master branch of the repository, use
composer install to include the dependencies. If you want to skip to a specific section, just checkout the associated git tag.
If PHP is not your language of choice, no problem. The concepts in general are common to any Web application. You'll find how to add second verification to a Web application even when not following along on your own.
Our Parse database structure is pretty simple, we have a
User class with
password, as well as a
ToDo class with a
todoItem and a pointer to the
user that owns the item. To set up your own copy, you can use this dump of the Parse schema or run
$php public/index.php setup parse after you've setup a Parse application.
The application itself is built as a rather standard Zend Framework 2 module based on the Action Controller concept. How the application is configured isn't too relevant to this example; however, the bulk of that is in the module configuration, where you'll see the application's routes defined.
There are only two controllers, one for the application itself (the
AppController) and one for creating accounts and authenticating (the
AuthController). Parse is really doing all the hard work for the
AuthController, creating users:
$user = new ParseUser(); $user->setUsername($email); $user->setPassword($password);
Logging them in:
$user = ParseUser::logIn($this->request->getPost('email'), $this->request->getPost('password')); $_SESSION['todo']['user'] = $user->getUsername();
Then redirecting to the
You can take a look at the entire
AuthController before 2FA is added.
For any requests to the Nexmo API we'll be using Philip Shipley's client library. It's a simple wrapper built on Guzzle and its Web Service Clients. We can add a factory to create and configure the client library when needed. That means we also need to add Nexmo API credentials to our local config file along with the Parse credentials.
Adding to Signup
Before enabling second factor when signing in, the ToDo list application needs to have the user's phone number. The easiest way to ensure we have that - and confirm that it is really the user's number - is use the Verify API to make number confirmation part of the signup process as well.
This also helps avoid spoof accounts, as a user must provide their phone number when they signup and we can force that number to be unique per user.
First we need to add a phone number field to our database. Since we're using Parse, we can go to the Parse dashboard and add a
phoneNumber string to the
User class. But we don't even have to do that, as setting the field will automatically create it in Parse.
We also need to add that field to the signup form, an easy addition to the signup template. Following the bootstrap borrowed markup, we just add another input element:
<label for="phone" class="sr-only">Phone Number</label> <input type="text" id="phone" name="phone" class="form-control" placeholder="Phone Number" required>
Now in the
AuthController we need to delay creating the user until they've verified the phone number they provided, and we've checked that the number is unique to their account. Verifying ownership of the phone number is where we start using Nexmo's Verify API.
Verifying a number takes two steps. First our application makes a verfy request to the API and gets a
request_id in response. This starts a process where the user is sent a numeric code by SMS (or should SMS not be successful, by a voice call).
Once the user provides that code to our application, an API request is made to the verify check endpoint to verify that the user provided the correct code. If they did, they've confirmed ownership of the device that was sent the code.
To keep things simple, we'll keep everything signup related submitting to the controller's
signupAction(). That means the method needs to be aware of two potential requests: the initial POST where the user provides their email, password, and phone number, and the follow up POST where the user provides the verification code they received on their phone.