Install a sms two factor authentication in Symfony2

Abstract:

This article aims to help you build a two step authentication with sms for your Symfony2 application. It works like the google two step authentication. Here is the workflow of the achieved feature:

  • the user fills in a first login form with his login and password
  • he receives an SMS with a one time code
  • he fills a second login form with the code
  • he can check a “I’m on a trusted computer” box so the second step will be skipped the next time he logs
  • he’s logged

We will also add some development tools:

  • a parameter to fallback to mails (useful in dev or test environment)
  • a parameter to add a master phone number (like the ‘delivery_address’ parameter of swiftmailer)
  • a functional test

Requirements

  • I use Nexmo as my sms sending service.
  • a functional Symfony2 project with FOSuser installed (FOSuser is not compulsory but it helps a lot doing it right and through)

1. Install bundles

We need to install two dependencies. The first, two-factor-bundle, will manage the second authentication step. The second, nexmo-bundle, will help us send sms easily.

    # composer.json
    {
        # ...
        "require": {
                # ...
            "scheb/two-factor-bundle": "0.3.*",
            "javihernandezgil/nexmo-bundle": "v0.9.*"
            # ...
        },
        # ...
    }

Register them in AppKernel :

    // app/AppKernel.php
    // ...
    class AppKernel extends Kernel
    {
        public function registerBundles()
        {
            $bundles = array(
                // ...
                new Scheb\TwoFactorBundle\SchebTwoFactorBundle(),
                new Jhg\NexmoBundle\JhgNexmoBundle(),
                // ...
            );
            // ...
        }
        // ...
    }

Then add some configuration:

    # app/config/config.yml
    # ...
    jhg_nexmo:
        api_key:    %nexmo_api_key%
        api_secret: %nexmo_api_secret%
        from_name:  %nexmo_from_name%
    # ...

nexmo_api_key, nexmo_api_secret, nexmo_from_name are parameters defined in app/config/parameters.yml. More details on these parameters are available in the two-factor bundle documentation and in the nexmo bundle documentation.

We will use two additional parameters along with them:

  • nexmo_delivery_phone_number: if set, all sms messages will be sent to this phone number instead of being sent to their actual recipients. This is often useful when developing.
  • nexmo_disable_delivery: if true, no sms will be delivered, mail will be send instead.

Eventually, our parameter file will look more or less like this:

    # app/config/parameters.yml
    ...
    nexmo_api_key: "12345abc"
    nexmo_api_secret: "67890def"
    nexmo_from_name: MyCompany
    nexmo_delivery_phone_number: "+33123456789"
    nexmo_disable_delivery: false

You can now run composer to process the install.

    composer install

2. Extend FOSUserBundle

This is the optional part. All we need is a bundle which implements a user entity. Extending FOSUserBundle is a secure and clean way to do so.

If you use FOSUserBundle then create a new bundle (I called it “AcmeUserBundle”) which extends “FOSUserBundle” as explained in the Symfony2 documentation.

4. Test your work

In a behat scenario we want to do things like this:

    Scenario: Login through login form
        Given I am on "/login"
        When I fill in "username" with "admin"
        And I fill in "password" with "admin"
        And I press "_submit"
        Then I fill the form with the validation code
        And I press "_submit"
        Then the url should match "/home"

Here is the custom behat step to do so:

    // Features/Context/FeatureContext.php
    /**
     * @Then /^I fill the form with the validation code$/
     */
    public function iFillTheValidationCodeForm()
    {
        $profiler = $this->getContainer()->get('profiler');
        $result = $profiler->find(null, null, 1, "POST", null, null);
        $profile = $profiler->loadProfile($result[0]['token']);

        $collector = $profile->getCollector('swiftmailer');
        $code = $collector->getMessages()[0]->getBody();
        return array(
            new Step\When('I fill in "_auth_code" with "'.$code.'"')
        );
    }

Resources

Have a look at Christian Scheb Blog

Special thanks to scheb and javihernandezgil for their fantastic work and availability.


You liked this article? You'd probably be a good match for our ever-growing tech team at Theodo.

Join Us