Adding an AJAX powered login form to a Symfony 2 project is pretty simple, but there are a few things to cover. The first is that the Symfony firewall handles authentication by sending a form to the route defined in the app/config/security.yml as the check_path for the firewall. So to login using AJAX, a form needs to be posted to that route along with a few fields, _username, _password, _remember_me, and if you’ve enabled CSRF for your form, the _csrf_token, field. The firewall will work its magic to login or reject the user, but then it will redirect to the login route, target path, or any of the routes set in the app/config/security.yml file. This is not conducive to an AJAX login where we need to be able to handle success or failure, and offer a message to the user. We need to create a class that will handle authentication success or failure and return a JSON response to make AJAX authentication possible. The Security chapter of the Symfony Cookbook is a great resource to start with, but it doesn’t really cover how to manage AJAX authentication.
The first step is to create the class that will handle authentication. Note that this class must implement the AuthenticationSuccessHandlerInterface and AuthenticationFailureHandlerInterface interfaces. The onAuthenticationSuccess and onAuthenticationFailure methods are part of those interfaces, and those are the methods that will be called to handle authentication. As you can see, we’re checking the Request to find out if it is an AJAX call by calling the isXmlHttpRequest() method. If it is an AJAX call we return a JSON response, and if it isn’t then we return a RedirectResponse to appropriate destination. In the onAuthenticationFailure method we even return the error message back to the client so it can be displayed to the user.
// AuthenticationHandler.php namespace path\to\your\class; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Security\Core\SecurityContextInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; class AuthenticationHandler implements AuthenticationSuccessHandlerInterface, AuthenticationFailureHandlerInterface { private $router; private $session; /** * Constructor * * @author Joe Sexton <joe@webtipblog.com> * @param RouterInterface $router * @param Session $session */ public function __construct( RouterInterface $router, Session $session ) { $this->router = $router; $this->session = $session; } /** * onAuthenticationSuccess * * @author Joe Sexton <joe@webtipblog.com> * @param Request $request * @param TokenInterface $token * @return Response */ public function onAuthenticationSuccess( Request $request, TokenInterface $token ) { // if AJAX login if ( $request->isXmlHttpRequest() ) { $array = array( 'success' => true ); // data to return via JSON $response = new Response( json_encode( $array ) ); $response->headers->set( 'Content-Type', 'application/json' ); return $response; // if form login } else { if ( $this->session->get('_security.main.target_path' ) ) { $url = $this->session->get( '_security.main.target_path' ); } else { $url = $this->router->generate( 'home_page' ); } // end if return new RedirectResponse( $url ); } } /** * onAuthenticationFailure * * @author Joe Sexton <joe@webtipblog.com> * @param Request $request * @param AuthenticationException $exception * @return Response */ public function onAuthenticationFailure( Request $request, AuthenticationException $exception ) { // if AJAX login if ( $request->isXmlHttpRequest() ) { $array = array( 'success' => false, 'message' => $exception->getMessage() ); // data to return via JSON $response = new Response( json_encode( $array ) ); $response->headers->set( 'Content-Type', 'application/json' ); return $response; // if form login } else { // set authentication exception to session $request->getSession()->set(SecurityContextInterface::AUTHENTICATION_ERROR, $exception); return new RedirectResponse( $this->router->generate( 'login_route' ) ); } } }
The next step is to register a service for this class in your bundle’s Resources/config/services.yml file:
# Resources/config/services.yml acme.security.authentication_handler: class: path\to\your\class\AuthenticationHandler public: false arguments: - @router - @session
The final step is to register this service as the handler for authentication success and failure in the app/config/security.yml file. To do so, simply add the success_handler and failure_handler parameters to your firewall configuration like this:
# app/config/security.yml security: firewalls: main: form_login: check_path: security_check_route success_handler: acme.security.authentication_handler failure_handler: acme.security.authentication_handler
I’ll leave the javascript implementation to you since that can vary dramatically depending on the frameworks you’re using, but simply make your AJAX call to the ‘security_check_route’ path as set in the check_path parameter in the security.yml file. Be sure to include the _username, _password, _remember_me, and if you’ve enabled CSRF for your form, the _csrf_token, fields in your POST request. If you want to passively listen to authentication success or failure, check out my article on creating a Symfony 2 authentication event listener.
Thanks alot. Why cant this be included in the cookbook
Thanks for this very helpful post.
If i can suggest one improvement, you can use the JsonResponse class instead of the Response class :
Looks like it didn’t like php code :/
This is really really useful! Thank you so much 🙂
There’s just one thing I didn’t fully understand. What is the corresponding path to “security_check_route” route? I mean what would be the url I’d send an ajax call to?
Hope, this makes sense!
Hello Davit,
this is the route corresponding to your login mechanism
e.g login form such as oauth_login. It point to the login form with the validate url login_check.
On the other hand just make an ajax call with parameters _username, _password and other fields necessary
Congratulations, excellent tutorial!
Please, could you post the working example:
– Form template (to check fields syntax and form structure)
– AuthenticationHandler (with real code)
– Controller’s action that validates user (security_check_route)
It would be a great help. I’m newbie.
Hi thank for tutorial… it work without problem, but now I have a problem when try to login in application I have this response “Your session has timed out, or you have disabled cookies” and I have a enable cookies. Please help!!!
Perfect! Just what I was looking for. Thanks!
Thanks!
That is a really cool post doing exactly what one need.
+1 to put this into Cookbook
Great tutorial, very simple to implement and efficient. You are giving a great service to the community, thanks.
Hi,
I used your method but when I submit the form, i get the error “Invalid CSRF token.”
Any idea ?
Thanks
Great article!! for those who use Symfony >= 2.6 :
// Symfony 2.5
use Symfony\Component\Security\Core\SecurityContextInterface;
if ($security->has(SecurityContextInterface::AUTHENTICATION_ERROR)) { … }
// Symfony 2.6
use Symfony\Component\Security\Core\Security;
if ($security->has(Security::AUTHENTICATION_ERROR)) { … }
If i send an ajax request with json body, it fails to log me in.
my symfony only accepts form url-encoded params.
any idea how can i make symfony accept json?
thanks