Integrating Two-Factor Authentication with Clef

Posted by Adam Engebretson on February 15, 2014

clef is a mobile app that replaces usernames and passwords with your smartphone.

Two-factor authentication (2FA) is become more and more popular as people become increasingly concerned with online security. Countless internet services are providing this extra level of security, including AWS, App.net, Apple ID, Blizzard (for you WoW players), Digital Ocean, Facebook, GitHub, and WordPress.

Because of this high demand for increased security, multiple services are popping up to make it easier to integrate 2FA into smaller web applications. See my previous post entitled Installing Two-Factor Authentication with Authy.

Clef is the new kid on the block, offering a less cumbersome and minimal effort form of providing the second-step authentication. Rather than entering a 4 or 8 digit code to authenticate, you simply scan a moving barcode with the Clef app on your mobile phone. The Clef service then provides you with an OAuth Authorization Code. Let's take a look at how it works! (You can follow along at the Clef documentation).

First, we'll need to install a Clef button. This button will request your Clef session using your Application's specific Application ID and Application Secret. Using the button generater at GetClef.com, I installed this button:

<script type="text/javascript" src="https://clef.io/v3/clef.js" class="clef-button" data-app-id="{My App ID}" data-color="white" data-style="flat" data-redirect-url="{{Request::root()}}/admin/login/clef"></script>

I used the {{Request::root()}} so I won't have to update the HTML when we put this app in development. Make a note, however, that the button will not work if it is installed on a domain other than the one you configred in your Clef Application at GetClef.com.

The Redirect URL points to the following route:

Route::get('admin/login/clef', 'AuthController@getLoginClef');

We can now begin the OAuth Handshake process. Next, in order to handle this request, we'll install "enygma/clef": "dev-master" using composer. Before I show you the getLoginClef() method, let me show you an IoC binding I've added to my app's Service Provider.

/* Clef Authentication */
$this->app->bind('clef', function()
{
  $clef = new \Clef\Request(Config::get('hfc.clef.id'), Config::get('hfc.clef.secret'));

  $clef->setClient(new \Guzzle\Http\Client);

  return $clef;
});

This will provide us with a convenient way to access the prepared Clef client via App::make('clef'). Now let's take a look at that getLoginClef() method on my AuthController.

public function getLoginClef()
{
  try {
    $clef = App::make('clef');
    $authorization = $clef->authenticate(Input::get('code'));

    if($authorization->success) {
      $user = $this->service->findByClef($clef->getUser());

      Auth::login($user);

      return Redirect::intended('/admin');
    }
  }
  catch (\Exception $e)
  {
    dd($e);
  }
}

Essentially, we receive an OAuth Authorization Code from the Input::get('code') (remember, we're currently handing the redirect callback), and pass it on to Clef's Authentication API. This will return an access token (added as a property to the $clef object) which can be used to fetch the user's information. $clef->getUser() will return a stdClass with two attributes: (bool) success and (stdClass) info.

My $this->service references a User Service I've injected into my AuthController. Here's the findByClef($user) method:

public function findByClef($clefUser)
{
  $info = $clefUser->info;

  $user = User::whereClefId($info->id)->first();

  if($user instanceof User)
    return $user;

  $user = User::whereEmail($info->email)->first();

  if($user instanceof User) {
    $user->clef_id = $info->id;
    $user->save();

    return $user;
  }

  throw new \Exception;
}

We'll just look for a user in my local database with either the clef_id or a matching email and return that user. Please note, however, that I have not put a lot of time into perfecting the exceptions in these code examples. It could definately be done better!

Looking at the previous code block, you'll now see that we simply Auth::login($user) or catch an exception. And that's it for logging in!

One thing unique to Clef is that they provide a way to manage logouts as well as logins. The Clef documentation puts it this way: "By integrating with Clef Logout, you put your users in control: single-sign-off is the best way for your users to manage their online identities." We'll dig into the Clef Logout methodology and practice in a later blog post.

Waltz is an implementation of Clef that stores your web application passwords securely on your computer as a Google Chrome plugin. It allows you to authenticate using Clef (as well as log out) to websites such as Amazon, CloudFlare, CodeSchool, Craigslist, Dropbox, Ebay, Facebook, GitHub, Google, Hacker News, Instapaper, Kahn Academy, LinkedIn, MailChimp, Netflix, New York Times, PayPal, Reddit, Target, The Verge, Trello, Tumblr, Twitter, Wells Fargo, Wordpress, YouTube, and duoLingo (Can you tell this was made by developers for developers?). If you enjoy using Clef, but your favorite website doesn't support Clef as a two-factor authentication option, Waltz might be worth checking out! The Chrome plguin has an interface built in for you to request additions to the list of compatable web apps.