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.

13 comments on “Adding an AJAX Login Form to a Symfony Project

  1. Moyo says:

    Thanks alot. Why cant this be included in the cookbook

  2. Xavier P says:

    Thanks for this very helpful post.
    If i can suggest one improvement, you can use the JsonResponse class instead of the Response class :

     false, 'message' => $exception->getMessage()]);
    
    1. Xavier P says:

      Looks like it didn’t like php code :/

  3. Davit says:

    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!

    1. Clement says:

      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

  4. Félix says:

    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.

  5. Marduk says:

    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!!!

  6. Almar says:

    Perfect! Just what I was looking for. Thanks!

  7. Roman Dushko says:

    Thanks!
    That is a really cool post doing exactly what one need.
    +1 to put this into Cookbook

  8. Nadjib says:

    Great tutorial, very simple to implement and efficient. You are giving a great service to the community, thanks.

  9. Symediane says:

    Hi,
    I used your method but when I submit the form, i get the error “Invalid CSRF token.”
    Any idea ?

    Thanks

  10. Marouen says:

    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)) { … }

  11. han solo says:

    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

Comments are closed.