How to redirect to different url based on roles in symfony 2
Solution 1
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);
}
}
Solution 2
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 }
Solution 3
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...
Solution 4
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.
Solution 5
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.
Related videos on Youtube
Mirage
Updated on September 14, 2022Comments
-
Mirage about 1 year
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?
-
Permana about 10 yearsI get error
Notice: Object of class Symfony\Component\HttpFoundation\ResponseHeaderBag could not be converted to int
, maybe because of + operator? -
TroodoN-Mike about 9 yearsperfect! Quick note. I did not have to define scope=request when registering listener (symfony2 v 2.4.8).
-
conceptdeluxe almost 6 years+1 Great approach! Based on your configuration it might be required to add
always_use_default_target_path: true
option as well. -
denkweite over 5 yearsI like this way! Thanks!
-
beta about 4 yearsThe 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 about 4 yearsAdding
$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. -
Darius.V over 3 yearsBut this makes 2 redirects and slows down, no?