How to Customize Access Denied Responses
In Symfony, you can throw an 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 generate 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
AuthenticationEntryPointInterface.
This interface has one method (start()) that is called whenever an
unauthenticated user tries to access a protected resource:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
// src/Security/AuthenticationEntryPoint.php
namespace App\Security;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
class AuthenticationEntryPoint implements AuthenticationEntryPointInterface
{
    public function __construct(
        private UrlGeneratorInterface $urlGenerator,
    ) {
    }
    public function start(Request $request, ?AuthenticationException $authException = null): RedirectResponse
    {
        // add a custom flash message and redirect to the login page
        $request->getSession()->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:
1 2 3 4 5 6 7
# config/packages/security.yaml
firewalls:
    # ...
    main:
        # ...
        entry_point: App\Security\AuthenticationEntryPoint
    Customize the Forbidden Response
Create a class that implements
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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// 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:
1 2 3 4 5 6 7
# config/packages/security.yaml
firewalls:
    # ...
    main:
        # ...
        access_denied_handler: App\Security\AccessDeniedHandler
    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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
// 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();
    }
}