Mobile Passwordless SMS Authentication: Part 1 – Building the API with Laravel and Twilio

January 27, 2015
Written by

Fallon-password

Lately there’s been a lot of discussion about the future of passwords – some have even declared that the password is obsolete. When I saw that one of my favorite apps, Cover, decided to launch passwordless authentication I was intrigued. I realized what a great experience passwordless auth provided after using it for the first time. Users don’t have to worry about remembering an overly complex password and since the SMS is delivered to their phone number they know it’s secure. In this series, I’ll show you how  to build your own basic mobile passwordless authentication system for both iOS and Android using Laravel and Twilio SMS. Today, we’ll kick the series off by building the API that will power our system.

See It In Action

If you truly want to experience the passwordless authentication experience I’d recommend downloading the Cover app and seeing how they do it:

In parts 2 and 3 of this series we’ll build a basic version of this flow for iOS and Android. It won’t look nearly as nice but it’ll give you a technical foundation for building passwordless authentication in your own mobile apps.

Our Tools

Our app will consist of two components:

  1. A client-side mobile experience built with Swift for iOS and Java for Android
  2. A server-side API built using Laravel

For now, we’re focusing on our backend which will have the following dependencies:

  • PHP >= 5.4

Laying Our Foundation

We’re going to build our API as a basic Laravel app that will allow us to determine which user is trying to authenticate, send them a one-time use token via SMS and validate that token. I decided to build the API using Laravel because it’s a framework I’ve been really enjoying recently but you can take these concepts and apply them to your language or framework of choice.

First we need to set up a new Laravel project that connects to a MySQL database. Let’s start by creating a new boilerplate Laravel project.

laravel new passwordless-auth
cd passwordless-auth/
php artisan serve

Visit http://localhost:8000 and you’ll see the beautiful Laravel welcome page.

For our application we’ll create a very basic user object and we’ll store that user data in a MySQL database. In order to properly connect to our database we need to update our database config files with the proper credentials. Open up app/config/database.php and look for the following section:

'mysql' => array(
  'driver'    => 'mysql',
  'host'      => 'localhost',
  'database'  => 'passwordless-auth',
  'username'  => 'forge',
  'password'  => '',
  'charset'   => 'utf8',
  'collation' => 'utf8_unicode_ci',
  'prefix'    => '',
),

This array configures the details for our MySQL connection. Make sure to update your host, database, username and password to the correct info for your database. You’ll need to create your database in MySQL for this code to work.

Now that we’ve properly configured our database we need to write the code that updates our schema with the data we want to capture. First we’ll create a migration that will create our user table:

php artisan migrate:make create_users_table --table=users --create

When you run this command Laravel will generate a new file in app/database/migrations/ with that will look something like this: [current_date]_create_users_table.php. This file will allow us to define specific schema changes we want to apply. Open up this file and modify the up function to contain the following code:


public function up()
{
  Schema::create('users', function(Blueprint $table)
  {
    $table->increments('id');
    $table->string('email')->unique();
    $table->string('phone_number');
    $table->timestamps();
  });
}

Next we’re going to seed our database by configuring our DatabaseSeeder to preload a single user into our users table. In our final application we’d want to have users signing up for our application but this single user will work for our prototype. Let’s open up /app/database/seeds/DatabaseSeeder.php and replace it with this code that will properly seed things for us:

<?php

class DatabaseSeeder extends Seeder {
/**
* Run the database seeds.
*
* @return void
*/
  public function run()
  {
    Eloquent::unguard();
    $this->call('UserTableSeeder');
  }
}

class UserTableSeeder extends Seeder {
  public function run()
  {
    DB::table('users')->delete();
    // make sure to update this code with your actual e-mail and phone number
    User::create(array('email' => 'hi@example.com','phone_number' => '+15555555555'));
  }
}

This code will delete our users table (in case we have any pre-existing data there for some reason) and insert a single user with the data in our array. Our user is very simple. It only contains an e-mail address and phone number. Make sure to update this information in the code to have your own info.

We’re almost ready to populate our database but let’s update our User model first. Open up app/models/User.php and add the highlighted line after the hidden properties are defined:


protected $hidden = array('password', 'remember_token');

protected $fillable = array('email', 'phone_number');

Now that we have all our pieces in place, let’s run our database migration (this will apply our schema changes) and seed our data:

php artisan migrate
php artisan db:seed

Knock Knock! Who’s There?

Now that we have our database in place, let’s update our User model to generate, send and validate our authentication token. We’ll be using Twilio to send our tokens so we need to require the Twilio PHP library using composer:

composer require twilio/sdk

We’ll store the token in a session and we should generate a random key to make sure our sessions are secure:

php artisan key:generate

One last piece of setup: let’s create an .env.php file to store our Twilio credentials and our Twilio number:

<?php
return array(
  'TWILIO_ACCOUNT_SID' => 'ACCOUNT_SID',
  'TWILIO_AUTH_TOKEN' => 'AUTH_TOKEN',
  'TWILIO_NUMBER' => 'TWILIO_NUMBER'
);

Make sure to update this information with the credentials in your Twilio Dashboard. We want this information to stay private so if you’re managing this project with git be sure to add this file to your .gitignore file as well.

Now let’s create a new method on our User model that generates and sends our token. We’ll add this function in app/models/User.php:

public function sendToken()
{
  $token = mt_rand(100000, 999999);
  Session::put('token', $token);

  $client = new Services_Twilio($_ENV['TWILIO_ACCOUNT_SID'], $_ENV['TWILIO_AUTH_TOKEN']);

  $sms = $client->account->messages->sendMessage(
    $_ENV['TWILIO_NUMBER'], // the text will be sent from your Twilio number
    $this->phone_number,
    "Your auth token is " . $token
  );

}

First, we generate a random six digit number and store it in our session. This will be our authentication token. Then we instantiate our Twilio PHP library. We use the sendMessage method to send a new message from our twilio number, to the users phone number and with a body notifying them of their token.

Next we can create new method on our User model that validates that a token is correct. We’ll add this code in app/models/User.php too:

public function validateToken($token)
{
  $validToken = Session::get('token');
  if($token == $validToken) {
    Session::forget('token');
    Auth::login($this);
    return true;
  } else {
    return false;
  }
}

In this code we’re pulling the valid token from our session and validating it against the token that was provided. If they match then we’re removing the the token from our session, authenticating the user for this app and returning true. Otherwise, we’re returning false.

Now that we’ve added the proper methods to our User model we can build the API endpoints that interact with these methods. Our API will have two endpoints:

  1. /user/validate – This endpoint will accept a phone number and send an authentication token to the user with that phone number.
  2. /user/auth – This endpoint will accept the authorization token for a user and log them in

In app/routes.php add the following two lines at the end of the file:

Route::post('user/validate/', 'UserController@validate');
Route::post('user/auth/', 'UserController@auth');

We’ve defined our two routes that will be accessed when someone makes a POST request to our server. Now we need to create a new file (app/controllers/UserController.php) that will serve as our controller for these two routes. We can start by making our base class and adding a method for our /user/validate route:

<?php

class UserController extends BaseController {

  function validate()
  {
    $phoneNum = Input::get('phone_num');

    $user = User::where('phone_number', '=', $phoneNum)->firstOrFail();
    if($user)
    {
      Session::put('phoneNum', $phoneNum);
      $user->sendToken();
      return Response::json(array('success' => true));
    } else
    {
       return Response::json(array('success' => false));
    }
  }
}

First we’re pulling the phone_num that is POSTed to this endpoint. Then we do a database lookup to determine if that user exists. If it does we’ll store our phoneNum in the session so we can access it later, call our sendToken function and return a simple JSON success response. If the user doesn’t exist we’ll send an unsuccessful JSON response. In the real world, we’d want to provide a more detailed reason for the failure but right now we’re just keeping things simple.

Now we can add the method for our /user/auth route inside the UserController:

function auth()
{
  $token = Input::get('token');
  $phoneNum = Session::get('phoneNum');
  $user = User::where('phone_number', '=', $phoneNum)->firstOrFail();
  if($user && $user->validateToken($token)) {
    return Response::json(array('success' => true));
  } else {
    return Response::json(array('success' => false));
  }
}

First, we’re getting the token that is POSTed to our endpoint and the phoneNum from our session. Next, we’ll check our database for a user with that phone number. If the user exists and the token is valid we’ll send back a successful json response. Otherwise we’ll send back back an unsuccessful response. Again, in the real world we’d want to provide a more detailed error from our API but this will work as a proof of concept.

Would You Like To Save Your Game?

Let’s create a checkpoint for ourselves and make sure everything works. On Halloween I showed you how to use Postman to debug your Twilio apps. Today we’re going to use it to test the endpoints we just built and make sure they’re working correctly. If you haven’t used Postman before, it’s is a really great tool for building and executing custom HTTP requests.

If you shut down your app to make these changes make sure to start it back up:

php artisan serve

Now let’s create a request to our /user/validate endpoint to trigger sending our SMS token:

If you haven’t used Postman before, you need to fill in url, phone_num and switch the method dropdown from GET to POST. Remember that we set up our app to only send a code if there’s a user in the database so make sure to use the phone number you configured when you seeded your database. Shortly after you make the request you should get your verification token.

Once you have your token we can make our user/auth request using postman:

This should return our success json and that means we’re all set on our API for now. If you experiencing any issues feel free to compare your code with the final version of this code in the github repo.

Accessing Secure Data

Our API is basic but hopefully you’re seeing the potential. There’s one last thing I want to show you how to do. We’re currently authenticating a user, but what about making a request to get data that only an authenticated user can receive? Luckily for us Laravel makes this really easy. Open up your app/routes.php file and add the following route:

Route::get('profile', array('before' => 'auth', function()
{
  // Only authenticated users may enter...
  return Response::json(array("secure_data" => "secret stuff!"));
}));

Restart your app and try to access the /profile route. If you’ve been following along with this tutorial, you’re already authenticated and you’ll notice you’re seeing our super secret data. We can quickly add a /user/logout route to allow us to see what happens when we access the /profile route when we’re not authenticated. Add the following method to your app/routes.php file:

Route::get('user/logout', function()
{
  Auth::logout();
  return Response::json(array("success" => true));
});

By default Laravel will redirect any unauthenticated requests we make for /profile to /login, let’s add a route to avoid receiving a 404 error when this occurs. Add your route in app/routes.php:

Route::get('login', function()
{
  return Response::json(array("error" => true));
});

Restart your server to make sure all our new routes are loaded. Now visit /user/logout and then try /profile. You’ll notice you’ve been redirected to /login and see our error JSON. Run through the authentication process in Postman one more time and once again you’ll get access to our super secret data.

What’s Next?

Whether or not you think passwords are obsolete, I hope this code helps serve as inspiration to think about passwords differently and perhaps even implement passwordless authentication in your own app. In part 2 of this series I’ll show you how to integrate this API into your iOS apps with Swift. Have questions or what to show off what you’ve built? Holler at me on twitter (@rickyrobinett) or e-mail (ricky@twilio.com)