Skip to content

Commit 8256c3d

Browse files
committed
Clarify authentication entry point and access denied handler
1 parent 4d52670 commit 8256c3d

File tree

1 file changed

+163
-16
lines changed

1 file changed

+163
-16
lines changed

security/access_denied_handler.rst

Lines changed: 163 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,117 @@
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
11-
:class:`Symfony\\Component\\Security\\Http\\Authorization\\AccessDeniedHandlerInterface`.
12-
This interface defines one method called ``handle()`` where you can implement whatever
13-
logic that should execute when access is denied for the current user (e.g. send a
14-
mail, log a message, or generally return a custom response)::
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+
To customize the handling of unauthenticated users, create a class that
22+
implements
23+
:class:`Symfony\\Component\\Security\\Http\\EntryPoint\\AuthenticationEntryPointInterface`.
24+
This interface has one method (``start()``) that is called whenever an
25+
unauthenticated user tries to access a protected resource::
26+
27+
namespace App\Security;
28+
29+
use Symfony\Component\HttpFoundation\Request;
30+
use Symfony\Component\HttpFoundation\RedirectResponse;
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 flashbag 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
109+
:class:`Symfony\\Component\\Security\\Http\\Authorization\\AccessDeniedHandlerInterface`
110+
to customize the access denied response for authenticated users. This
111+
interface defines one method called ``handle()`` where you can implement
112+
whatever logic that should execute when access is denied for the current
113+
user (e.g. send a mail, log a message, or generally return a custom
114+
response)::
15115

16116
namespace App\Security;
17117

@@ -49,11 +149,21 @@ configure it under your firewall:
49149
.. code-block:: xml
50150
51151
<!-- config/packages/security.xml -->
52-
<config>
53-
<firewall name="main">
54-
<access-denied-handler>App\Security\AccessDeniedHandler</access-denied-handler>
55-
</firewall>
56-
</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>
57167
58168
.. code-block:: php
59169
@@ -69,5 +179,42 @@ configure it under your firewall:
69179
],
70180
]);
71181
72-
That's it! Any ``AccessDeniedException`` thrown by code under the ``main`` firewall
73-
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+
namespace App\EventListener;
190+
191+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
192+
use Symfony\Component\HttpFoundation\Response;
193+
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
194+
use Symfony\Component\HttpKernel\KernelEvents;
195+
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
196+
197+
class AccessDeniedListener implements EventSubscriberInterface
198+
{
199+
public static function getSubscribedEvents(): array
200+
{
201+
return [
202+
// the priority must be >1, to make sure it's called before
203+
// the default exception listener
204+
KernelEvents::EXCEPTION => ['onKernelException', 2],
205+
];
206+
}
207+
208+
public function onKernelException(ExceptionEvent $event): void
209+
{
210+
$exception = $event->getException();
211+
if (!$exception instanceof AccessDeniedException) {
212+
return;
213+
}
214+
215+
// ... perform some action (e.g. logging)
216+
217+
// optionally set the custom response
218+
$event->setResponse(new Response(null, 403));
219+
}
220+
}

0 commit comments

Comments
 (0)