Two-Factor Authentication in Symfony2

Before you start reading:
If you just want to add two-factor authentication to your project, you should take a look at scheb/two-factor-bundle. If you’re interested in technical details, keep on reading 🙂


For a project of mine I wanted to have some extra security because it contains critical features, only authorized people should have access to in any case. So I did some research if it’s possible to implement two-factor authentication in Symfony2. Sadly I didn’t find any good how-tos about that topic. Then I’ve found out that SonataUserBundle has Google Authenticator as an optional feature, so I did some reverse enginering to figure out how they did it.

This is how you implement two-factor authentication into Symfony2’s security layer. The following example will send out a random code to the user’s email address. I will do another post for Google Authenticator soon.

The basic principle is quite simple:

  • Do authentication with any kind of authentication provider (e.g. default username/password authentication).
  • Listen for the security.interactive_login event and check if user has two-factor authentication enabled. If that’s the case, add a new attribute to the session, which flags the user with “authentication not completed”.
  • Listen for requests (kernel.request event) and check if the attribute is set in the session. As long as the authentication is not complete, display a page with a form and ask for the authentication code. The content originally requested will not be shown.
  • Check if the request contains an authentication code. If the code is correct, set the attribute in the session to “authentication complete”. Now the user is fully authenticated and can access the secured area.

So let’s go! I’m assuming you’re using a Doctrine entity for the user object. First, we have to create two additional attributes. Also add the getter and setter methods. $twoFactorAuthentication will activate two-factor authentication on a user, $twoFactorCode will be used to store the authentication code.

Then create a helper class, which will generate the authentication code, send the email and take care of the flag in the session.

Now we need to create two listeners. This one will listen for successful authentication. It checks if the user supports two-factor authentication. If that’s the case the session attribute will be added and the authentication code will be sent to the user’s email address.

The next class will listen for any kind of requests. It checks if the user is still flagged with “authentication not complete”, then a form is displayed. When the request contains an authenticaton code, it is validated. If it’s wrong, a flash message is set and the form is displayed again. If it’s corrent the flag in the session is updated and the user will be forwarded to the dashboard.

This is the twig template used to ask for the authentication code:

The logout link is there so users that can’t complete the second step have a way out. Otherwise they will be forever stuck with the authentication form (or at least until the session expires).

Now register everything as a service in the bundle’s services.xml (I prefer creating a security.xml for security-related services).

And that’s it. Now every user with two-factor authentication enabled will get the authentication code dialog after a successful login. The secured area will only become accessible when the correct authentication code is entered.

Another post covering the integration of Google Authenticator will follow soon.
Update: Here it is.

If you have any alternative concepts how to integrate two-factor authentication or you know some improvements for my implementation, please let me know.

11 thoughts on “Two-Factor Authentication in Symfony2

  • Pingback: Google Authenticator in Symfony2 | SchebBlog

  • December 24, 2013 at 00:18
    Permalink

    This works great. I’ve been struggling to understand how to correctly use helpers and listeners. Reading your code line-by-line has shown me how to correctly implement and pass parameters as required.

    Now onto your next blog of the Google Two Factor Authentication.

    Reply
  • January 20, 2015 at 22:47
    Permalink

    Hello. You have mistakes in your code.
    1. You must use Symfony\Bundle\FrameworkBundle\Routing\Router in RequestListener
    2. You must give one more argument in services.xml for acme_user.twofactor.email.request_listener such as ‘@router’. Because in code you use $this->router
    3. You don`t have close form tag in template

    Reply
  • February 19, 2015 at 12:14
    Permalink

    Thanks for the post, really helpful. One thing though: Your second factor form isn’t protected against CSRF attacks. You should inject the form factory into the request listener an use the form builder to create and verify forms.

    Reply
  • July 6, 2015 at 10:01
    Permalink

    If you want to use css/javascripts in confirmation code form you need to modify RequestListener/onCoreRequest method.

    if (substr($request->attributes->get('_route'),0,9) == '_assetic_')
    {
    return;
    }

    Also, if you want profiler to work:

    if( in_array($request->attributes->get('_route'), array('_wdt','_profiler','_profiler_search_bar','_profiler_router')))
    {
    return;
    }

    Reply
  • Pingback: Install a sms two factor authentication in Symfony2 | Theodo, dĂ©veloppement agile symfony

  • October 21, 2016 at 18:28
    Permalink

    Hi Christian,

    Thank you for your tutorial, which has been really helpful!
    I understand that when the user is logged, we redirect him to another form where he will enter the authentication code. I was wondering if there was an easy way to show him the second form in the same page, using Ajax for ex ?

    Reply
  • November 19, 2018 at 02:57
    Permalink

    Thanks a lot for the tutorial. Really useful!

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.