4
votes

I'm trying to make the AJAX authentication work with FOSUserBundle.

I have created an Handler directory with a AuthenticationHandler class :

<?php

namespace BirdOffice\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;

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

    /**
     * onAuthenticationSuccess
     *
     * @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
     *
     * @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' ) );
        }
    }
}

I have created a login Javascript function :

function login() {
    $.ajax({
        type: "POST",
        url: Routing.generate('check_login_ajax'),
        dataType: 'json',
        data: {
            _username: $('#username').val(),
            _password: $('#password').val(),
            _remember_me: false,
            _csrf_token: $('#_csrf_token').val()
        }
    }).done(function(data) {
        console.log(data);
    }).fail(function(data) {
        console.log(data);
    });
}

In my routingAjax.yml, I have added the following lines to override the FOSUserBundle security route :

check_login_ajax:
    pattern:  /check_login_ajax
    defaults: { _controller: FOSUserBundle:Security:check }
    requirements:
        _method:  POST
    options:
        expose: true

In my global security.yml file, I have added the check_path, success_handler and failure_handler parts :

firewalls:
        main:
            pattern: ^/
            form_login:
                login_path: fos_user_registration_register
                check_path:      check_login_ajax
                success_handler: user.security.authentication_handler
                failure_handler: user.security.authentication_handler
                provider: fos_userbundle
                csrf_provider: form.csrf_provider
            logout:
                  path:   fos_user_security_logout
                  target: /
            anonymous:    true

My first issue is : the AJAX return this message: "Invalid CSRF token." (but I send a good one generated in PHP, maybe I missed something doing it). Here is my PHP code for it :

<?php
  $csrfProvider = $this->container->get('form.csrf_provider');
  $csrfToken = $csrfProvider->generateCsrfToken('popUpUser');
?>

Second issue : my login page (not the AJAX one) is not working anymore because the orignal route of FOSUserBundle login has been overwritten.

PS : I have posted a message yesterday : FOSUserBundle (login / register) + AJAX + Symfony2 but I have badly explained my problem. Sorry by advance.

1
if you are using symfony2.4 the generation of token has changed use security.csrf.token_manager instead of form.csrf_providerzizoujab
Thank you for you help, I'm using Symfony 2.6.4. I just replace the following : <?php $csrfProvider = $this->container->get('form.csrf_provider'); $csrfToken = $csrfProvider->generateCsrfToken('popUpUser'); ?> By : <?php $csrf = $this->get('security.csrf.token_manager'); $csrfToken = $csrf->refreshToken('popUpUser'); ?> But stil have the same error message.user4457363

1 Answers

5
votes

First Issue: You are sending an invalid CSRF token. In Symfony 2.3 you could generate it using {{ csrf_token('authenticate') }} inside the template's input's value.

Second issue: Do not overwrite the route, simply use the original route: fos_user_security_check.

In general: if you use an AuthenticationSuccessHandler extending Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler your method could look something like this:

public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
    if ($request->isXmlHttpRequest()) {
        return new JsonResponse(array('success' => true));
    }

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

Do something similar for an AuthenticationFailureHandler extending Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler.