Skip to content

Commit 2cd9032

Browse files
committed
minor #14045 [Security] Clarify the purpose of access denied handler (and entry point) (wouterj)
This PR was merged into the 4.4 branch. Discussion ---------- [Security] Clarify the purpose of access denied handler (and entry point) There is some confusion about the AccessDeniedHandler - mostly due to the way we've documented it. This PR tries to fix it by documenting what the AccessDeniedHandler is doing, how the other cases can be catched (using an entry point) and how you can catch all access denied exceptions (using an exception listener). Commits ------- 3892e26 Clarify authentication entry point and access denied handler
2 parents 4cb6b21 + 3892e26 commit 2cd9032

File tree

1 file changed

+166
-15
lines changed

1 file changed

+166
-15
lines changed

security/access_denied_handler.rst

Lines changed: 166 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,116 @@
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

Comments
 (0)