Question

I need to implement a two step (not two-factor) authentication in Symfony 2.3. The first step is the usual user+password+csrf form. The second step is "Terms & Conditions", which the user should see when they first log in or when the terms are updated, and they should have to tick a box in order to proceed to the rest of the site.

The second step isn't really an authentication step, but a user shouldn't be able to access the rest of the site unless that second step is acted upon, so it makes sense to conceptually think of it as part of the authentication.

While writing this, the rubber duck tells me that I should think about authorisation instead, and the idea of starting the user on a "didn't accept terms yet" role, and updating the role to "fully authorised user" if the terms have been accepted. This sounds like the most sound solution so far, as I can let the firewall take care of the logic.

Stumbled upon these pieces of information so far:
http://blogsh.de/2011/11/15/change-user-roles-during-a-session-in-symfony/
http://php-and-symfony.matthiasnoback.nl/2012/07/symfony2-security-creating-dynamic-roles-using-roleinterface/

There's one behaviour that I expect to encounter as I dig deeper into this: the firewall will display an error instead of re-directing the user to the Terms page and then let them on their way once they accept them.

Has anybody done this before, so I have to invent as little of the wheel as possible?

Was it helpful?

Solution

I found somebody with a similar problem, and he received a solution I could use:

Symfony 2 : Redirect a user to a page if he has a specific role

The event listener class:

namespace Acme\DemoBundle\Lib;

use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\HttpKernel;

use Acme\DemoBundle\Entity\User;

class TermsAndConditionsRequestListener
{
    private $security;
    private $router;

    public function __construct($security, $router)
    {
        $this->security = $security;
        $this->router = $router;
    }

    public function onKernelRequest(GetResponseEvent $event)
    {
        /* http://symfony.com/doc/2.3/cookbook/service_container/event_listener.html */
        if (HttpKernel::MASTER_REQUEST !== $event->getRequestType())
        {
            // don't do anything if it's not the master request
            return;
        }

        $request = $event->getRequest();
        $route = $request->attributes->get('_route');

        if ($route === '_wdt' || substr_compare($route, '_profiler', 0, 9) === 0)
        {
            // ignore development routes
            return;
        }

        if (in_array($route, array('terms_and_conditions_force', 'terms_and_conditions_accept')))
        {
            // don't redirect into an infinite loop
            return;
        }

        $token = $this->security->getToken();
        $user = $token ? $token->getUser() : null;
        $user_role = ($user instanceof User) ? $user->getRole() : null;

        if ($user_role === 'ROLE_USER' && (is_null($user->getTermsAcceptedDate()) || $terms_are_newer_than_acceptance_date))
        {
            $url = $this->router->generate('terms_and_conditions_force');
            $event->setResponse(new RedirectResponse($url));
        }
    }
}

The event listener service:

acme.wvml.event_listener.request.terms_and_conditions:
    class: Acme\DemoBundle\Lib\TermsAndConditionsRequestListener
    arguments: [@security.context, @router]
    tags:
        - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }

OTHER TIPS

You will have to extend the symfony UserAuthenticationProvider. you'll probably want to add the check in the checkAuthentication function and if it fails return the error message regarding the terms and conditions.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top