0
votes

I am using Symfony 2.3 with FOSUserBundle and want to make a login form with AJAX. I found a view questions on SO (like this one) and even some other helpful sites (Adding an AJAX Login Form to a Symfony Project) that explained, how to use FOSUserBundle with ajax.

Everything works fine when I submit the form regularly, but using the ajax call I keep getting the message "Invalid CSRF token.".

Here are my configurations:

The Custom AuthenticationHandler:

namespace Moodio\Bundle\UserBundle\Handler;

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;
    private $translator;
    private $csrf_provider;

    /**
     * Constructor
     */
    public function __construct( RouterInterface $router, Session $session, $translator, $csrf_provider )
    {
        $this->router  = $router;
        $this->session = $session;
        $this->translator = $translator;
        $this->csrf_provider = $csrf_provider;
    }

    /**
     * onAuthenticationSuccess
     */
    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;

        } else {// if form login

            return parent::onAuthenticationSuccess($request, $token);
        }
    }

    /**
     * onAuthenticationFailure
     *
     */
    public function onAuthenticationFailure( Request $request, AuthenticationException $exception )
    {
        // if AJAX login
        if ( $request->isXmlHttpRequest() ) {
            $result = array(
                'success' => false,
                'message' => $this->translator->trans($exception->getMessage(), array(), 'FOSUserBundle')
            ); // data to return via JSON

            $response = new Response( json_encode( $result ) );
            $response->headers->set( 'Content-Type', 'application/json' );

            return $response;


        } else {// if form login

            // set authentication exception to session
            $request->getSession()->set(SecurityContextInterface::AUTHENTICATION_ERROR, $exception);

            return new RedirectResponse( $this->router->generate( 'fos_user_security_login' ) );
        }
    }
}

src/Moodio/Bundle/MainBundle/services.yml:

services:
    moodio_main.security.authentication_handler:
        class: Moodio\Bundle\UserBundle\Handler\AuthenticationHandler
        public: false
        arguments:
            - @router
            - @session
            - @translator
            - @form.csrf_provider

security.yml firewalls:

    main:
        pattern: ^/
        form_login:
            provider: fos_userbundle
            csrf_provider: form.csrf_provider
            success_handler: moodio_main.security.authentication_handler
            failure_handler: moodio_main.security.authentication_handler
        logout:       true
        anonymous:    true

my javascript

$('#_submit').click(function(e){
    e.preventDefault();
    var frm = $('form');
    $.ajax({
        type        : frm.attr('method'),
        url         : frm.attr('action'),
        data        : frm.serialize(),
        success     : function(data, status, object) {
            if(data.error) $('.error').html(data.message);
        },
        error: function(data, status, object){
            console.log(data.message);
        }
    });
});

I tried some answers like in this answer, but it did not work for me. I actually don't understand why it should make a difference, because FOSUserBundle already creates the csrf-token and includes it in the twig template with {{ csrf_token }}.

At the moment I am using the standard FOSUserBundle templates (and only added the view lines of js I needed for the ajax).

One thing I also tried just to see if the csrf validation works, is to generate the csrf token in the onAuthenticationFailure function and check if it is valid just in the next line. In this case isCsrfTokenValid() returned true.

When I deactivate the csrf_provider in the firewall (in security.yml) I always get the message "Bad credentials" even though the credentials are right.

2
I don't understand the reason, why you want to implement login using AJAX. Because on login Symfony2 or any custom application will initiate SESSION, will write some COOKIES to identify the established connection. And for that browser will need a refresh. Use AJAX after login, but login users without AJAX. Unless if you are developing a Webservice for that you can use token-based authentication without initiating session, as all clients like native mobile apps won't initiate SESSIONS easily.Imran Zahoor

2 Answers

0
votes

If you are using the very latest version of FOSUserBundle, the csrf provider has changed to security.csrf.token_manager. Your form could be generating the token with form.csrf_provider and FOSUB could be using another. Either:

  1. explicitly set your FOSUserBundle dependency to 1.2 or 1.3, and not to dev-master
  2. Manually set the csrf provider to the proper service.

Either way, clear your cookies and restart your dev server to make sure there is no legacy information in there.

0
votes

I finally could solve my problem:

I found the Divi-AjaxLoginBundle which works like a charm. I am not sure, what the problem was, because I had actually the same AuthenticationHandlers like this bundle, but there are also some files in the DependencyInjection folder which maybe did the job.

Thanks to all who tried to help me.