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:

  1. // src/Security/AuthenticationEntryPoint.php
  2. namespace App\Security;
  3. use Symfony\Component\HttpFoundation\RedirectResponse;
  4. use Symfony\Component\HttpFoundation\Request;
  5. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  6. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  7. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  8. use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
  9. class AuthenticationEntryPoint implements AuthenticationEntryPointInterface
  10. {
  11. private $urlGenerator;
  12. private $session;
  13. public function __construct(UrlGeneratorInterface $urlGenerator, SessionInterface $session)
  14. {
  15. $this->urlGenerator = $urlGenerator;
  16. $this->session = $session;
  17. }
  18. public function start(Request $request, AuthenticationException $authException = null): RedirectResponse
  19. {
  20. // add a custom flash message and redirect to the login page
  21. $this->session->getFlashBag()->add('note', 'You have to login in order to access this page.');
  22. return new RedirectResponse($this->urlGenerator->generate('security_login'));
  23. }
  24. }

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. # config/packages/security.yaml
    2. firewalls:
    3. # ...
    4. main:
    5. # ...
    6. entry_point: App\Security\AuthenticationEntryPoint
  • XML

    1. <!-- config/packages/security.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <srv:container xmlns="http://symfony.com/schema/dic/security"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xmlns:srv="http://symfony.com/schema/dic/services"
    6. xsi:schemaLocation="http://symfony.com/schema/dic/services
    7. https://symfony.com/schema/dic/services/services-1.0.xsd">
    8. <config>
    9. <firewall name="main"
    10. entry-point="App\Security\AuthenticationEntryPoint"
    11. >
    12. <!-- ... -->
    13. </firewall>
    14. </config>
    15. </srv:container>
  • PHP

    1. // config/packages/security.php
    2. use App\Security\AuthenticationEntryPoint;
    3. $container->loadFromExtension('security', [
    4. 'firewalls' => [
    5. 'main' => [
    6. // ...
    7. 'entry_point' => AuthenticationEntryPoint::class,
    8. ],
    9. ],
    10. ]);

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):

  1. // src/Security/AccessDeniedHandler.php
  2. namespace App\Security;
  3. use Symfony\Component\HttpFoundation\Request;
  4. use Symfony\Component\HttpFoundation\Response;
  5. use Symfony\Component\Security\Core\Exception\AccessDeniedException;
  6. use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface;
  7. class AccessDeniedHandler implements AccessDeniedHandlerInterface
  8. {
  9. public function handle(Request $request, AccessDeniedException $accessDeniedException): ?Response
  10. {
  11. // ...
  12. return new Response($content, 403);
  13. }
  14. }

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. # config/packages/security.yaml
    2. firewalls:
    3. # ...
    4. main:
    5. # ...
    6. access_denied_handler: App\Security\AccessDeniedHandler
  • XML

    1. <!-- config/packages/security.xml -->
    2. <?xml version="1.0" encoding="UTF-8" ?>
    3. <srv:container xmlns="http://symfony.com/schema/dic/security"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xmlns:srv="http://symfony.com/schema/dic/services"
    6. xsi:schemaLocation="http://symfony.com/schema/dic/services
    7. https://symfony.com/schema/dic/services/services-1.0.xsd">
    8. <config>
    9. <firewall name="main"
    10. access-denied-handler="App\Security\AccessDeniedHandler"
    11. >
    12. <!-- ... -->
    13. </firewall>
    14. </config>
    15. </srv:container>
  • PHP

    1. // config/packages/security.php
    2. use App\Security\AccessDeniedHandler;
    3. $container->loadFromExtension('security', [
    4. 'firewalls' => [
    5. 'main' => [
    6. // ...
    7. 'access_denied_handler' => AccessDeniedHandler::class,
    8. ],
    9. ],
    10. ]);

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. // src/EventListener/AccessDeniedListener.php
  2. namespace App\EventListener;
  3. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  4. use Symfony\Component\HttpFoundation\Response;
  5. use Symfony\Component\HttpKernel\Event\ExceptionEvent;
  6. use Symfony\Component\HttpKernel\KernelEvents;
  7. use Symfony\Component\Security\Core\Exception\AccessDeniedException;
  8. class AccessDeniedListener implements EventSubscriberInterface
  9. {
  10. public static function getSubscribedEvents(): array
  11. {
  12. return [
  13. // the priority must be greater than the Security HTTP
  14. // ExceptionListener, to make sure it's called before
  15. // the default exception listener
  16. KernelEvents::EXCEPTION => ['onKernelException', 2],
  17. ];
  18. }
  19. public function onKernelException(ExceptionEvent $event): void
  20. {
  21. $exception = $event->getThrowable();
  22. if (!$exception instanceof AccessDeniedException) {
  23. return;
  24. }
  25. // ... perform some action (e.g. logging)
  26. // optionally set the custom response
  27. $event->setResponse(new Response(null, 403));
  28. // or stop propagation (prevents the next exception listeners from being called)
  29. //$event->stopPropagation();
  30. }
  31. }

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