David's answer is good, but it's lacking a little detail for newbs - so this is to fill in the blanks.

In addition to creating the AuthenticationHandler you'll need to set it up as a service using the service configuration in the bundle where you created the handler. The default bundle generation creates an xml file, but I prefer yml. Here's an example services.yml file:

#src/Vendor/BundleName/Resources/config/services.yml

parameters:
    vendor_security.authentication_handler: Vendor\BundleName\Handler\AuthenticationHandler

services:
    authentication_handler:
        class:  %vendor_security.authentication_handler%
        arguments:  [@router]
        tags:
            - { name: 'monolog.logger', channel: 'security' }

You'd need to modify the DependencyInjection bundle extension to use yml instead of xml like so:

#src/Vendor/BundleName/DependencyInjection/BundleExtension.php

$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');

Then in your app's security configuration you set up the references to the authentication_handler service you just defined:

# app/config/security.yml

security:
    firewalls:
        secured_area:
            pattern:    ^/
            anonymous: ~
            form_login:
                login_path:  /login
                check_path:  /login_check
                success_handler: authentication_handler
                failure_handler: authentication_handler

namespace YourVendor\UserBundle\Handler;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;

class AuthenticationHandler
implements AuthenticationSuccessHandlerInterface,
           AuthenticationFailureHandlerInterface
{
    private $router;

    public function __construct(Router $router)
    {
        $this->router = $router;
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token)
    {
        if ($request->isXmlHttpRequest()) {
            // Handle XHR here
        } else {
            // If the user tried to access a protected resource and was forces to login
            // redirect him back to that resource
            if ($targetPath = $request->getSession()->get('_security.target_path')) {
                $url = $targetPath;
            } else {
                // Otherwise, redirect him to wherever you want
                $url = $this->router->generate('user_view', array(
                    'nickname' => $token->getUser()->getNickname()
                ));
            }

            return new RedirectResponse($url);
        }
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        if ($request->isXmlHttpRequest()) {
            // Handle XHR here
        } else {
            // Create a flash message with the authentication error message
            $request->getSession()->setFlash('error', $exception->getMessage());
            $url = $this->router->generate('user_login');

            return new RedirectResponse($url);
        }
    }
}

If you want the FOS UserBundle form error support, you must use:

$request->getSession()->set(SecurityContext::AUTHENTICATION_ERROR, $exception);

instead of:

$request->getSession()->setFlash('error', $exception->getMessage());

In the first answer.

(of course remember about the header: use Symfony\Component\Security\Core\SecurityContext;)


I handled this entirely with javascript:

if($('a.login').length > 0) { // if login button shows up (only if logged out)
        var formDialog = new MyAppLib.AjaxFormDialog({ // create a new ajax dialog, which loads the loginpage
            title: 'Login',
            url: $('a.login').attr('href'),
            formId: '#login-form',
            successCallback: function(nullvalue, dialog) { // when the ajax request is finished, look for a login error. if no error shows up -> reload the current page
                if(dialog.find('.error').length == 0) {
                    $('.ui-dialog-content').slideUp();
                    window.location.reload();
                }
            }
        });

        $('a.login').click(function(){
            formDialog.show();
            return false;
        });
    }

Here is the AjaxFormDialog class. Unfortunately I have not ported it to a jQuery plugin by now... https://gist.github.com/1601803