Skip to content

Commit ba824f7

Browse files
committed
Clarify authentication entry point and access denied handler
1 parent 8672801 commit ba824f7

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
namespace App\Security;
17116

@@ -49,11 +148,21 @@ configure it under your firewall:
49148
.. code-block:: xml
50149
51150
<!-- config/packages/security.xml -->
52-
<config>
53-
<firewall name="main">
54-
<access-denied-handler>App\Security\AccessDeniedHandler</access-denied-handler>
55-
</firewall>
56-
</config>
151+
<?xml version="1.0" encoding="UTF-8"?>
152+
<srv:container xmlns="http://symfony.com/schema/dic/security"
153+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
154+
xmlns:srv="http://symfony.com/schema/dic/services"
155+
xsi:schemaLocation="http://symfony.com/schema/dic/services
156+
https://symfony.com/schema/dic/services/services-1.0.xsd">
157+
158+
<config>
159+
<firewall name="main"
160+
access-denied-handler="App\Security\AccessDeniedHandler"
161+
>
162+
<!-- ... -->
163+
</firewall>
164+
</config>
165+
</srv:container>
57166
58167
.. code-block:: php
59168
@@ -69,5 +178,47 @@ configure it under your firewall:
69178
],
70179
]);
71180
72-
That's it! Any ``AccessDeniedException`` thrown by code under the ``main`` firewall
73-
will now be handled by your service.
181+
Customizing All Access Denied Responses
182+
---------------------------------------
183+
184+
In some cases, you might want to customize both responses or do a specific
185+
action (e.g. logging) for each ``AccessDeniedException``. In this case,
186+
configure a :ref:`kernel.exception listener <use-kernel-exception-event>`::
187+
188+
// src/EventListener/AccessDeniedListener.php
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 greater than the Security HTTP
203+
// ExceptionListener (1), to make sure it's called before
204+
// the default exception listener
205+
KernelEvents::EXCEPTION => ['onKernelException', 2],
206+
];
207+
}
208+
209+
public function onKernelException(ExceptionEvent $event): void
210+
{
211+
$exception = $event->getException();
212+
if (!$exception instanceof AccessDeniedException) {
213+
return;
214+
}
215+
216+
// ... perform some action (e.g. logging)
217+
218+
// optionally set the custom response
219+
$event->setResponse(new Response(null, 403));
220+
221+
// or stop propagation (prevents the other exception listeners from being called)
222+
//$event->stopPropagation();
223+
}
224+
}

0 commit comments

Comments
 (0)