Redirecting on login/logout in Symfony2 using LoginHandlers.
Filed under Programming, Projects
Using login handlers or even logout handlers, we can do a number of last minute things before the user is redirected to where they need to go. Being able to intercept the redirect at the last minute and make the user get redirected elsewhere is relatively simple.
Firstly though, i notice a lot of people now who are using Symfony 2 are cheating somewhat by using an EventListener and registering it with the security.interactive_login event listener. This is not the best way to go about this, namely because this event listener is not intended for this purpose. Sometimes event listeners are not the best way to go because they were defined for a specific purpose other than your intention and using them may have undesirable side effects you may not notice right away. Using handlers however allows a little more flexibility in that in all likelihood regardless of changes to Symfony in future revisions, they should still work and were designed specifically for the purposes we will need them for.
Firstly, we need to define our services, personally, i am a fan of YAML, so i define my services as such but feel free to do the same in XML:
parameters:
code_consortium_user.login_success_handler.class: CodeConsortiumUserBundleServiceLoginSuccessHandler
code_consortium_user.logout_success_handler.class: CodeConsortiumUserBundleServiceLogoutSuccessHandler
services:
login_success_handler:
class: %code_consortium_user.login_success_handler.class%
arguments: [@router, @security.context]
tags:
- { name: 'monolog.logger' channel: 'security' }
logout_success_handler:
class: %code_consortium_user.logout_success_handler.class%
arguments: [@router]
tags:
- { name: 'monolog.logger' channel: 'security' }
Now our services are defined, we need to define the handlers themselves. I like to create a directory named Services in my user bundle for this purpose, then create 2 files. The first file is LoginSuccessHandler, the second is LogoutSuccessHandler. Our handlers will implement the appropriate interface for either our login or logout handlers, which require usually only one method in these instances.
Here is what the login handler should look like:
<?php
namespace CodeConsortiumUserBundleService;
use SymfonyComponentSecurityHttpAuthenticationAuthenticationSuccessHandlerInterface;
use SymfonyComponentSecurityCoreAuthenticationTokenTokenInterface;
use SymfonyComponentSecurityCoreSecurityContext;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationRedirectResponse;
use SymfonyComponentRoutingRouter;
class LoginSuccessHandler implements AuthenticationSuccessHandlerInterface
{
protected $router;
protected $security;
public function __construct(Router $router, SecurityContext $security)
{
$this->router = $router;
$this->security = $security;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
if ($this->security->isGranted('ROLE_SUPER_ADMIN'))
{
$response = new RedirectResponse($this->router->generate('category_index'));
}
elseif ($this->security->isGranted('ROLE_ADMIN'))
{
$response = new RedirectResponse($this->router->generate('category_index'));
}
elseif ($this->security->isGranted('ROLE_USER'))
{
// redirect the user to where they were before the login process begun.
$referer_url = $request->headers->get('referer');
$response = new RedirectResponse($referer_url);
}
return $response;
}
}
Note my namespace is CodeConsortium, and my bundle is called UserBundle, so our service is named code_consortium_user. Change this to your own namespace and bundle name accordingly but ensure to use the same format as used here, underscore your namespace and append the bundle name with an additional underscore without the term ‘bundle’ in it. Failure to get this part correct will mean your namespace will not be loaded and you will get problems later on in this tutorial as exceptions will be thrown regarding the service not existing.
Here is what the logout handler should look like:
<?php
namespace CodeConsortiumUserBundleService;
use SymfonyComponentSecurityHttpLogoutLogoutSuccessHandlerInterface;
use SymfonyComponentSecurityCoreAuthenticationTokenTokenInterface;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationRedirectResponse;
use SymfonyBundleFrameworkBundleRoutingRouter;
class LogoutSuccessHandler implements LogoutSuccessHandlerInterface
{
protected $router;
public function __construct(Router $router)
{
$this->router = $router;
}
public function onLogoutSuccess(Request $request)
{
// redirect the user to where they were before the login process begun.
$referer_url = $request->headers->get('referer');
$response = new RedirectResponse($referer_url);
return $response;
}
}
Our service definitions inject some of the classes we need to make use of our handlers, namely a Router object and the SecurityContext object, we can store these in member variables for later use. Then we wait for the onAuthenticationSuccess method to be called in our LoginSuccessHandler, once this has happened, we have a request and token interface object at our disposal. Using the security context object we can determine what role they have (i.e; ROLE_USER, ROLE_ADMIN etc) and appropriately redirect them as needed.
I like to redirect members of ROLE_USER role to where they came from prior to logging in. This is achieved by passing the ‘referer’ header into the redirect response object we return. I took this step both in the login and logout handlers.
Last thing we now need to do, is inform our security config that it needs to use these handlers by doing the following:
security:
providers:
fos_userbundle:
id: fos_user.user_manager
firewalls:
main:
#pattern: .*
form_login:
provider: fos_userbundle
login_path: /login
use_forward: false
check_path: /login_check
success_handler: login_success_handler
failure_path: null
logout:
path: /logout
target: /
success_handler: logout_success_handler
anonymous: true
In this instance i am using FOSUserBundle and extending it in my own UserBundle, which is probably the best way to do this but if you are using another user bundle or have written your own this should still work, just change mention of FOSUserBundle to reference your own.
Once this is implemented your on your way to a better login/logout system. Enjoy.
Oct26













October 28, 2011 at 10:08 pm
Excellent! I was trying to find some information to insert a flash message when the user logout but without reimplementing the whole logout system.
Thank you for the article!
October 28, 2011 at 10:12 pm
By the way, I can’t see the \ or / in your code, notice it in the “use” headers
October 29, 2011 at 6:00 am
@Silence i believe its wordpress or something stripping out the backslashes. Go to the source code on my github and then you can look under the UserBundle and then under Handlers directory. There you will see the full source code with the backslashes.
October 31, 2011 at 8:25 pm
Thanks Reece! I figure out where to put the backslashes and it worked fine.
I have a problem right now with the logout success handler. All I wanted to do was to store a flash var to store a goodbye message before redirecting the user to the login page. I tried this, but it doesn’t show me the session var in the login page
Can you give me a hand, please?
public function onLogoutSuccess(Request $request)
{
/**
* @var Symfony\Component\HttpFoundation\Session $session
*/
$session = $request->getSession();
$session->set(‘alert’, array (
‘msg’ => ‘panel.logout’,
‘class’ => ‘p_right’
));
return new RedirectResponse($this->router->generate(‘login’));
}
The method is being called but the session var is missing after the redirect
October 31, 2011 at 8:46 pm
@var i think this is because the session you are addressing is from the Request object, but after logout that session gets destroyed. You will likely need to create a new session in your logout handler. I have never done that before though so cannot advise. But that is what i would do, create a new session using Symfony’s Session class. Hope that helps, but if not then check out the IRC room, on irc.freenode #symfony.
good luck.
October 31, 2011 at 9:10 pm
you are right. Even it doesn’t make too much sense to me, because I think that the logout success handler should be cause AFTER the symfony logout stuff…
I’m very newbie on Symfony2, I’ll have to read more about it or forget about this feature right now.
Something I know that is going to work would be to create a redirect response that redirects to a dummy logout when I can set the message and redirect to the login form after that, but too much overhead to accomplish a simple task.
Another cool thing might be to have a “just_logout” var or method in your controller. I implemented it in my own framework. The credentials logout method, logouts the user but sets a flag that I can use in my controller to set the goodbye message or whatever I want… I’m missing something like that with this symfony system
October 31, 2011 at 9:42 pm
I think I found the solution. I’m using the LogoutHandlerInterface instead of the LogoutSuccessHandlerInterface.
In the security configuration you have to set the “handler” instead of the “success_handler” and in my new class I just have (see below):
It allows me to invalidate the session (which logouts the user) but I can set the flash var after that ^_^
Two days to do such a simple task, but at least I’m learning Symfony2
Regards
getSession();
$session->invalidate();
$session->setFlash(‘alert’, array (
‘msg’ => ‘panel.logout’,
‘class’ => ‘p_right’
));
}
}
November 1, 2011 at 9:21 am
@silence congrats on getting it working. There were times before when i too have spent a day or 2 working on getting something working. There are still things i am learning myself about Symfony.
November 24, 2011 at 5:18 pm
Hi,
I was exactly looking for a way to do this, I just have one question:
Where does this line points to:
id: fos_user.user_manager
?
I just can’t put this code to work because of this line.
Best regards.
November 25, 2011 at 4:10 am
@Xocoatzin
That line is for the FOSUserBundle, which you can download from github. It is a user bundle that handles user registration, login, account changes,change of password, account recovery and lastly account activation via email.
The line in question is used by FOSUserBundle to state which user_manager to use, which is a custom class in FOSUserBundle to handle the User entities various interactions, which should be mostly database related stuff.
If your not using FOSUserBundle then simply remove that line and hopefully you won’t have any issues. The configurations that are used in the app/config.yml file must all exist somewhere in Symfony or in the various bundles. They are defined in a bundles DependencyInjection folder, in the Configuration.php file. If no bundle or Symfony itself has defined a configuration option that is present in the app/config.yml then Symfony will spit out an error/exception. Or putting it another way, you can only put configurations into your app/config.yml that have been defined in the various Configuration.php files of each bundle under the tree builder.
Hope this helps and good luck.
November 25, 2011 at 11:21 pm
You’re rigth, I removed that line and worked perfectly. This was a great tutorial. Thanks !!
April 8, 2012 at 6:06 pm
Thanks a lot! This helped me when I wondered what to return in my login handler…