17

I have one login page on site. I have 4 different tye of users and i want that when they login they go to different page based on their role assigned.

Is there any way?

crmpicco
  • 16,605
  • 26
  • 134
  • 210
Mirage
  • 30,868
  • 62
  • 166
  • 261

7 Answers7

31

One way to solve this is to use an event listener on the security.interactive_login event. In this case I simply attach another listener in that event listener so it will fire on the response. This lets the authentication still happen but still perform a redirect once complete.

<service id="sotb_core.listener.login" class="SOTB\CoreBundle\EventListener\SecurityListener" scope="request">
    <tag name="kernel.event_listener" event="security.interactive_login" method="onSecurityInteractiveLogin"/>
    <argument type="service" id="router"/>
    <argument type="service" id="security.context"/>
    <argument type="service" id="event_dispatcher"/>
</service>

And the class...

class SecurityListener
{
    protected $router;
    protected $security;
    protected $dispatcher;

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

    public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
    {
        $this->dispatcher->addListener(KernelEvents::RESPONSE, array($this, 'onKernelResponse'));
    }

    public function onKernelResponse(FilterResponseEvent $event)
    {
        if ($this->security->isGranted('ROLE_TEAM')) {
            $response = new RedirectResponse($this->router->generate('team_homepage'));
        } elseif ($this->security->isGranted('ROLE_VENDOR')) {
            $response = new RedirectResponse($this->router->generate('vendor_homepage'));
        } else {
            $response = new RedirectResponse($this->router->generate('homepage'));
        }

        $event->setResponse($response);
    }
}
MDrollette
  • 6,887
  • 1
  • 36
  • 49
  • perfect! Quick note. I did not have to define scope=request when registering listener (symfony2 v 2.4.8). – TroodoN-Mike Aug 21 '14 at 09:48
  • 1
    Adding `$response->headers = $event->getResponse()->headers;` will save the original response headers, complete with cookie. This was important on my project to keep the REMEMBERME flag. – beta Sep 09 '19 at 22:44
10

For Symfony >= 2.6 now would be:

<?php

namespace CommonBundle\Listener;

use Monolog\Logger;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\Router;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;

class LoginListener
{
    /** @var Router */
    protected $router;

    /** @var TokenStorage */
    protected $token;

    /** @var EventDispatcherInterface */
    protected $dispatcher;

    /** @var Logger */
    protected $logger;

    /**
     * @param Router $router
     * @param TokenStorage $token
     * @param EventDispatcherInterface $dispatcher
     * @param Logger $logger
     */
    public function __construct(Router $router, TokenStorage $token, EventDispatcherInterface $dispatcher, Logger $logger)
    {
        $this->router       = $router;
        $this->token        = $token;
        $this->dispatcher   = $dispatcher;
        $this->logger       = $logger;
    }

    public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
    {
        $this->dispatcher->addListener(KernelEvents::RESPONSE, [$this, 'onKernelResponse']);
    }

    public function onKernelResponse(FilterResponseEvent $event)
    {
        $roles = $this->token->getToken()->getRoles();

        $rolesTab = array_map(function($role){
            return $role->getRole();
        }, $roles);

        $this->logger->info(var_export($rolesTab, true));

        if (in_array('ROLE_ADMIN', $rolesTab) || in_array('ROLE_SUPER_ADMIN', $rolesTab)) {
            $route = $this->router->generate('backend_homepage');
        } elseif (in_array('ROLE_CLIENT', $rolesTab)) {
            $route = $this->router->generate('frontend_homepage');
        } else {
            $route = $this->router->generate('portal_homepage');
        }

        $event->getResponse()->headers->set('Location', $route);
    }
}

And services.yml

services:
common.listener.login:
    class: CommonBundle\Listener\LoginListener
    arguments: [@router, @security.token_storage, @event_dispatcher, @logger]
    scope: request
    tags:
        - { name: kernel.event_listener, event: security.interactive_login, method: onSecurityInteractiveLogin }
8

Tested in Symfony 3.1

You could also set default path after user login successfully for all users in security.yml file like so:

[config/security.yml]

...

firewalls:
    # disables authentication for assets and the profiler, adapt it according to your needs
    dev:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false
    main:
        pattern: /.*
        form_login:
            login_path: /login
            check_path: /login_check
            default_target_path: /login/redirect <<<<<<<<<<<<<<<<<<<<<<<<<
        logout:
            path: /logout
            target: /
        security: true
        anonymous: ~
...

and then in default_target_path method make simple redirection based on user role. Very straight forward. Some say that the easiest way is always the best way. You decide :)

[SomeBundle/Controller/SomeController.php]

/**
 * Redirect users after login based on the granted ROLE
 * @Route("/login/redirect", name="_login_redirect")
 */
public function loginRedirectAction(Request $request)
{

    if (!$this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY'))
    {
        return $this->redirectToRoute('_login');
        // throw $this->createAccessDeniedException();
    }

    if($this->get('security.authorization_checker')->isGranted('ROLE_ADMIN'))
    {
        return $this->redirectToRoute('_admin_panel');
    }
    else if($this->get('security.authorization_checker')->isGranted('ROLE_USER'))
    {
        return $this->redirectToRoute('_user_panel');
    }
    else
    {
        return $this->redirectToRoute('_login');
    }
}

Works like a charm but keep in mind to always check for most restricted roles downwards in case your ROLE_ADMIN also has privileges of ROLE_USER and so on...

Manuel
  • 836
  • 13
  • 30
DevWL
  • 17,345
  • 6
  • 90
  • 86
5

I used Mdrollette answer but this solution has a big drawback, you completely override the symfony original response and by doing this remove the remember me cookie that was set in the header by symfony.

my solution was to change the OnKernelResponse this way :

public function onKernelResponse(FilterResponseEvent $event)
{
    if ($this->security->isGranted('ROLE_TEAM')) {
        $event->getResponse()->headers->set('Location', $this->router->generate('team_homepage'));    
    } elseif ($this->security->isGranted('ROLE_VENDOR')) {
        $event->getResponse()->headers->set('Location', $this->router->generate('vendor_homepage'));
    } else {
        $event->getResponse()->headers->set('Location', $this->router->generate('homepage'));
    }
}

This way you remain the remember me cookie intact.

Tanariel
  • 239
  • 3
  • 4
  • The cookie remains intact but if you were trying to access a specific page or file and needed to log in for it, this will always redirect you to the homepage instead of taking you directly to that file/page. – beta Sep 09 '19 at 22:40
2

If you are looking for a simpler answer than @MDrollette, you could put a similar redirect block into the controller of your login success page.

MrGlass
  • 9,094
  • 17
  • 64
  • 89
0

For the sake of testing, if you're wanting to to preserve the original response you could also just copy the headers. The clone method on the Redirect object only copies the headers.

public function onKernelResponse(FilterResponseEvent $event)
{
    if ($this->security->isGranted('ROLE_TEAM')) {
        $response = new RedirectResponse($this->router->generate('team_homepage'));
    } elseif ($this->security->isGranted('ROLE_VENDOR')) {
        $response = new RedirectResponse($this->router->generate('vendor_homepage'));
    } else {
        $response = new RedirectResponse($this->router->generate('homepage'));
    }

    $response->headers = $response->headers + $event->getResponse()->headers;

    $event->setResponse($response);
}
Ryan
  • 4,594
  • 1
  • 32
  • 35
  • I get error `Notice: Object of class Symfony\Component\HttpFoundation\ResponseHeaderBag could not be converted to int`, maybe because of + operator? – Permana Sep 13 '13 at 03:10
0

I used this in the login Form authenticator to redirect user based on role (symfony : 4.26.8) :

use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Security;

private $urlGenerator;
/**
 * @var Security
 */
private $security;

public function __construct(UrlGeneratorInterface $urlGenerator ,Security $security)
{
    $this->urlGenerator = $urlGenerator;
    $this->security = $security;
}

public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {        
    // redirecting user by role : 
        $user = $this->security->getUser();
        $roles = $user->getRoles();
        $rolesTab = array_map(function($role){
            return $role;
        }, $roles);
        if (in_array('ROLE_ADMIN', $rolesTab) || in_array('ROLE_SUPER_ADMIN', $rolesTab)) {
            return new RedirectResponse($this->urlGenerator->generate('admin'));
        }
        else{
            return new RedirectResponse($this->urlGenerator->generate('home'));
    }
}