Warning: You are browsing the documentation for Symfony 5.2, which is no longer maintained.

Read the updated version of this page for Symfony 5.3 (the current stable version).

How to Customize Access Denied Responses

How to Customize Access Denied Responses

In Symfony, you can throw an Symfony\Component\Security\Core\Exception\AccessDeniedException to disallow access to the user. Symfony will handle this exception and generates a response based on the authentication state:

  • If the user is not authenticated (or authenticated anonymously), an authentication entry point is used to generated a response (typically a redirect to the login page or an 401 Unauthorized response);
  • If the user is authenticated, but does not have the required permissions, a 403 Forbidden response is generated.

Customize the Unauthorized Response

You need to create a class that implements Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface. This interface has one method (start()) that is called whenever an unauthenticated user tries to access a protected resource:

// src/Security/AuthenticationEntryPoint.php
namespace App\Security;

use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;

class AuthenticationEntryPoint implements AuthenticationEntryPointInterface
{
    private $urlGenerator;
    private $session;

    public function __construct(UrlGeneratorInterface $urlGenerator, SessionInterface $session)
    {
        $this->urlGenerator = $urlGenerator;
        $this->session = $session;
    }

    public function start(Request $request, AuthenticationException $authException = null): RedirectResponse
    {
        // add a custom flash message and redirect to the login page
        $this->session->getFlashBag()->add('note', 'You have to login in order to access this page.');

        return new RedirectResponse($this->urlGenerator->generate('security_login'));
    }
}

That’s it if you’re using the default services.yaml configuration. Otherwise, you have to register this service in the container.

Now, configure this service ID as the entry point for the firewall:

  • YAML
    1
    2
    3
    4
    5
    6
    7
    # config/packages/security.yaml
    firewalls:
        # ...
    
        main:
            # ...
            entry_point: App\Security\AuthenticationEntryPoint
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    <!-- config/packages/security.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <srv:container xmlns="http://symfony.com/schema/dic/security"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:srv="http://symfony.com/schema/dic/services"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            https://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <config>
            <firewall name="main"
                entry-point="App\Security\AuthenticationEntryPoint"
            >
                <!-- ... -->
            </firewall>
        </config>
    </srv:container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    // config/packages/security.php
    use App\Security\AuthenticationEntryPoint;
    
    $container->loadFromExtension('security', [
        'firewalls' => [
            'main' => [
                // ...
                'entry_point' => AuthenticationEntryPoint::class,
            ],
        ],
    ]);
    

Customize the Forbidden Response

Create a class that implements Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface. This interface defines one method called handle() where you can implement whatever logic that should execute when access is denied for the current user (e.g. send a mail, log a message, or generally return a custom response):

// src/Security/AccessDeniedHandler.php
namespace App\Security;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface;

class AccessDeniedHandler implements AccessDeniedHandlerInterface
{
    public function handle(Request $request, AccessDeniedException $accessDeniedException): ?Response
    {
        // ...

        return new Response($content, 403);
    }
}

If you’re using the default services.yaml configuration, you’re done! Symfony will automatically know about your new service. You can then configure it under your firewall:

  • YAML
    1
    2
    3
    4
    5
    6
    7
    # config/packages/security.yaml
    firewalls:
        # ...
    
        main:
            # ...
            access_denied_handler: App\Security\AccessDeniedHandler
    
  • XML
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    <!-- config/packages/security.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <srv:container xmlns="http://symfony.com/schema/dic/security"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:srv="http://symfony.com/schema/dic/services"
        xsi:schemaLocation="http://symfony.com/schema/dic/services
            https://symfony.com/schema/dic/services/services-1.0.xsd">
    
        <config>
            <firewall name="main"
                access-denied-handler="App\Security\AccessDeniedHandler"
            >
                <!-- ... -->
            </firewall>
        </config>
    </srv:container>
    
  • PHP
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    // config/packages/security.php
    use App\Security\AccessDeniedHandler;
    
    $container->loadFromExtension('security', [
        'firewalls' => [
            'main' => [
                // ...
                'access_denied_handler' => AccessDeniedHandler::class,
            ],
        ],
    ]);
    

Customizing All Access Denied Responses

In some cases, you might want to customize both responses or do a specific action (e.g. logging) for each AccessDeniedException. In this case, configure a kernel.exception listener:

// src/EventListener/AccessDeniedListener.php
namespace App\EventListener;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

class AccessDeniedListener implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            // the priority must be greater than the Security HTTP
            // ExceptionListener, to make sure it's called before
            // the default exception listener
            KernelEvents::EXCEPTION => ['onKernelException', 2],
        ];
    }

    public function onKernelException(ExceptionEvent $event): void
    {
        $exception = $event->getThrowable();
        if (!$exception instanceof AccessDeniedException) {
            return;
        }

        // ... perform some action (e.g. logging)

        // optionally set the custom response
        $event->setResponse(new Response(null, 403));

        // or stop propagation (prevents the next exception listeners from being called)
        //$event->stopPropagation();
    }
}

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.