Symfony2 extending DefaultAuthenticationSuccessHandler

If you only have one success / failure handler defined for your application, there’s a slightly easier way to do this. Rather than define a new service for the success_handler and failure_handler, you can override security.authentication.success_handler and security.authentication.failure_handler instead.

Example:

services.yml

services:
    security.authentication.success_handler:
        class:  StatSidekick\UserBundle\Handler\AuthenticationSuccessHandler
        arguments:  ["@security.http_utils", {}]
        tags:
            - { name: 'monolog.logger', channel: 'security' }

    security.authentication.failure_handler:
        class:  StatSidekick\UserBundle\Handler\AuthenticationFailureHandler
        arguments:  ["@http_kernel", "@security.http_utils", {}, "@logger"]
        tags:
            - { name: 'monolog.logger', channel: 'security' }

AuthenticationSuccessHandler.php

<?php
namespace StatSidekick\UserBundle\Handler;

use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler;
use Symfony\Component\Security\Http\HttpUtils;

class AuthenticationSuccessHandler extends DefaultAuthenticationSuccessHandler {

    public function __construct( HttpUtils $httpUtils, array $options ) {
        parent::__construct( $httpUtils, $options );
    }

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

AuthenticationFailureHandler.php

<?php
namespace StatSidekick\UserBundle\Handler;

use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler;
use Symfony\Component\Security\Http\HttpUtils;

class AuthenticationFailureHandler extends DefaultAuthenticationFailureHandler {

    public function __construct( HttpKernelInterface $httpKernel, HttpUtils $httpUtils, array $options, LoggerInterface $logger = null ) {
        parent::__construct( $httpKernel, $httpUtils, $options, $logger );
    }

    public function onAuthenticationFailure( Request $request, AuthenticationException $exception ) {
        if( $request->isXmlHttpRequest() ) {
            $response = new JsonResponse( array( 'success' => false, 'message' => $exception->getMessage() ) );
        } else {
            $response = parent::onAuthenticationFailure( $request, $exception );
        }
        return $response;
    }
}

In my case, I was just trying to set something up so that I could get a JSON response when I try to authenticate using AJAX, but the principle is the same.

The benefit of this approach is that without any additional work, all of the options that are normally passed into the default handlers should get injected correctly. This happens because of how SecurityBundle\DependencyInjection\Security\Factory is setup in the framework:

protected function createAuthenticationSuccessHandler($container, $id, $config)
{
    ...
    $successHandler = $container->setDefinition($successHandlerId, new DefinitionDecorator('security.authentication.success_handler'));    
    $successHandler->replaceArgument(1, array_intersect_key($config, $this->defaultSuccessHandlerOptions));
    ...
}

protected function createAuthenticationFailureHandler($container, $id, $config)
{
    ...
    $failureHandler = $container->setDefinition($id, new DefinitionDecorator('security.authentication.failure_handler'));
    $failureHandler->replaceArgument(2, array_intersect_key($config, $this->defaultFailureHandlerOptions));
    ...
}

It specifically looks for security.authentication.success_handler and security.authentication.failure_handler in order to merge options from your config into the arrays passed in. I’m sure there’s a way to setup something similar for your own service, but I haven’t looked into it yet.

Hope that helps.

Leave a Comment