11.. index ::
22 single: Security; Creating a Custom Access Denied Handler
33
4- How to Create a Custom Access Denied Handler
5- ============================================
4+ How to Customize Access Denied Responses
5+ ========================================
66
7- When your application throws an ``AccessDeniedException ``, you can handle this exception
8- with a service to return a custom response.
7+ In Symfony, you can throw an
8+ :class: `Symfony\\ Component\\ Security\\ Core\\ Exception\\ AccessDeniedException `
9+ to disallow access to the user. Symfony will handle this exception and
10+ generates a response based on the authentication state:
911
10- First, create a class that implements
12+ * **If the user is not authenticated ** (or authenticated anonymously), an
13+ authentication entry point is used to generated a response (typically
14+ a redirect to the login page or an *401 Unauthorized * response);
15+ * **If the user is authenticated, but does not have the required
16+ permissions **, a *403 Forbidden * response is generated.
17+
18+ Customize the Unauthorized Response
19+ -----------------------------------
20+
21+ You need to create a class that implements
22+ :class: `Symfony\\ Component\\ Security\\ Http\\ EntryPoint\\ AuthenticationEntryPointInterface `.
23+ This interface has one method (``start() ``) that is called whenever an
24+ unauthenticated user tries to access a protected resource::
25+
26+ // src/Security/AuthenticationEntryPoint.php
27+ namespace App\Security;
28+
29+ use Symfony\Component\HttpFoundation\RedirectResponse;
30+ use Symfony\Component\HttpFoundation\Request;
31+ use Symfony\Component\HttpFoundation\Session\SessionInterface;
32+ use Symfony\Component\Security\Core\Exception\AuthenticationException;
33+ use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
34+
35+ class AuthenticationEntryPoint implements AuthenticationEntryPointInterface
36+ {
37+ private $urlGenerator;
38+ private $session;
39+
40+ public function __construct(UrlGeneratorInterface $urlGenerator, SessionInterface $session)
41+ {
42+ $this->urlGenerator = $urlGenerator;
43+ $this->session = $session;
44+ }
45+
46+ public function start(Request $request, AuthenticationException $authException = null): RedirectResponse
47+ {
48+ // add a custom flash message and redirect to the login page
49+ $this->session->getFlashBag()->add('note', 'You have to login in order to access this page.');
50+
51+ return new RedirectResponse($this->urlGenerator->generate('security_login'));
52+ }
53+ }
54+
55+ That's it if you're using the :ref: `default services.yaml configuration <service-container-services-load-example >`.
56+ Otherwise, you have to register this service in the container.
57+
58+ Now, configure this service ID as the entry point for the firewall:
59+
60+ .. configuration-block ::
61+
62+ .. code-block :: yaml
63+
64+ # config/packages/security.yaml
65+ firewalls :
66+ # ...
67+
68+ main :
69+ # ...
70+ entry_point : App\Security\AuthenticationEntryPoint
71+
72+ .. code-block :: xml
73+
74+ <!-- config/packages/security.xml -->
75+ <?xml version =" 1.0" encoding =" UTF-8" ?>
76+ <srv : container xmlns =" http://symfony.com/schema/dic/security"
77+ xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
78+ xmlns : srv =" http://symfony.com/schema/dic/services"
79+ xsi : schemaLocation =" http://symfony.com/schema/dic/services
80+ https://symfony.com/schema/dic/services/services-1.0.xsd" >
81+
82+ <config >
83+ <firewall name =" main"
84+ entry-point =" App\Security\AuthenticationEntryPoint"
85+ >
86+ <!-- ... -->
87+ </firewall >
88+ </config >
89+ </srv : container >
90+
91+ .. code-block :: php
92+
93+ // config/packages/security.php
94+ use App\Security\AuthenticationEntryPoint;
95+
96+ $container->loadFromExtension('security', [
97+ 'firewalls' => [
98+ 'main' => [
99+ // ...
100+ 'entry_point' => AuthenticationEntryPoint::class,
101+ ],
102+ ],
103+ ]);
104+
105+ Customize the Forbidden Response
106+ --------------------------------
107+
108+ Create a class that implements
11109:class: `Symfony\\ Component\\ Security\\ Http\\ Authorization\\ AccessDeniedHandlerInterface `.
12- This interface defines one method called ``handle() `` where you can implement whatever
13- logic that should run when access is denied for the current user (e.g. send a
14- mail, log a message, or generally return a custom response)::
110+ This interface defines one method called ``handle() `` where you can
111+ implement whatever logic that should execute when access is denied for the
112+ current user (e.g. send a mail, log a message, or generally return a custom
113+ response)::
15114
16115 // src/Security/AccessDeniedHandler.php
17116 namespace App\Security;
@@ -50,11 +149,21 @@ configure it under your firewall:
50149 .. code-block :: xml
51150
52151 <!-- config/packages/security.xml -->
53- <config >
54- <firewall name =" main" >
55- <access-denied-handler >App\Security\AccessDeniedHandler</access-denied-handler >
56- </firewall >
57- </config >
152+ <?xml version =" 1.0" encoding =" UTF-8" ?>
153+ <srv : container xmlns =" http://symfony.com/schema/dic/security"
154+ xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
155+ xmlns : srv =" http://symfony.com/schema/dic/services"
156+ xsi : schemaLocation =" http://symfony.com/schema/dic/services
157+ https://symfony.com/schema/dic/services/services-1.0.xsd" >
158+
159+ <config >
160+ <firewall name =" main"
161+ access-denied-handler =" App\Security\AccessDeniedHandler"
162+ >
163+ <!-- ... -->
164+ </firewall >
165+ </config >
166+ </srv : container >
58167
59168 .. code-block :: php
60169
@@ -70,5 +179,47 @@ configure it under your firewall:
70179 ],
71180 ]);
72181
73- That's it! Any ``AccessDeniedException `` thrown by code under the ``main `` firewall
74- will now be handled by your service.
182+ Customizing All Access Denied Responses
183+ ---------------------------------------
184+
185+ In some cases, you might want to customize both responses or do a specific
186+ action (e.g. logging) for each ``AccessDeniedException ``. In this case,
187+ configure a :ref: `kernel.exception listener <use-kernel-exception-event >`::
188+
189+ // src/EventListener/AccessDeniedListener.php
190+ namespace App\EventListener;
191+
192+ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
193+ use Symfony\Component\HttpFoundation\Response;
194+ use Symfony\Component\HttpKernel\Event\ExceptionEvent;
195+ use Symfony\Component\HttpKernel\KernelEvents;
196+ use Symfony\Component\Security\Core\Exception\AccessDeniedException;
197+
198+ class AccessDeniedListener implements EventSubscriberInterface
199+ {
200+ public static function getSubscribedEvents(): array
201+ {
202+ return [
203+ // the priority must be greater than the Security HTTP
204+ // ExceptionListener, to make sure it's called before
205+ // the default exception listener
206+ KernelEvents::EXCEPTION => ['onKernelException', 2],
207+ ];
208+ }
209+
210+ public function onKernelException(ExceptionEvent $event): void
211+ {
212+ $exception = $event->getException();
213+ if (!$exception instanceof AccessDeniedException) {
214+ return;
215+ }
216+
217+ // ... perform some action (e.g. logging)
218+
219+ // optionally set the custom response
220+ $event->setResponse(new Response(null, 403));
221+
222+ // or stop propagation (prevents the next exception listeners from being called)
223+ //$event->stopPropagation();
224+ }
225+ }
0 commit comments