Reputation: 11
I've written an API that returns JSON. Certain routes are secured by a @Security annotation on the Controller Action.
If the is_granted() method fails, I catch the thrown exception and output some error json with an 403 http status code.
This works, but only if the user is logged in, but hasn't enough rights. If not logged in, the user is redirected to the login page (not useful at all in an ajax call).
What can I do to prevent that redirect?
I've tried to add following line to the security.yml access_control section, but with no effect:
access_control:
- { path: ^/api, role: IS_AUTHENTICATED_ANONYMOUSLY }
Upvotes: 1
Views: 1226
Reputation: 1517
I wrote something very similar for Symfony 4.
But in my code, there is no need for checking the request URI because only the master request is checked. Also, the code is cleaner. The AccessDeniedException
from the Security Bundle is replaced by a AccessDeniedHttpException
from Symfony itself. This leads to a real 403 Exception page without losing the Debug possibilities.
// PHP class: App\EventListener\RestSecurity403ExceptionListener
namespace App\EventListener;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class RestSecurity403ExceptionListener
{
public function onKernelException(GetResponseForExceptionEvent $event)
{
// only check master request
if (!$event->isMasterRequest())
return;
// get variables
$exception = $event->getException();
$request = $event->getRequest();
// replace Security Bundle 403 with Symfony 403
if($exception instanceof AccessDeniedException)
throw new AccessDeniedHttpException("Symfony 403 error thrown instead of 403 error of the security bundle");
}
}
And also add the Exception Listener in your services.yaml
:
# services.yaml
services:
my.RestSecurity403ExceptionListener:
class: App\EventListener\RestSecurity403ExceptionListener
tags:
- { name: kernel.event_listener, event: kernel.exception, priority: 256 }
That's it.
Upvotes: 1
Reputation: 11
Ok, after hours of debugging I found out that this behaviour is hardcoded in the exception listener of the Security Component (Symfony\Component\Security\Http\Firewall\ExceptionListener).
So I had to write my own ExceptionListener, with an onKernelException Method:
public function onKernelException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
do {
if ($exception instanceof AccessDeniedException) {
if(substr($event->getRequest()->server->get('PATH_INFO'), 0, 4) == '/api') {
$event->setException(new AjaxAccessDeniedException());
}
}
} while (null !== $exception = $exception->getPrevious());
}
that checks if the path begins with /api, and throws my own AjaxAccessDeniedException. This Exception has the same Code as the AccessDeniedException, but does not inherit of it (because otherwise it would be caught by the Security Component ExceptionListener again). This one I can catch in the Exception Controller, because it doesn't get caught elsewhere.
The last step was to register my ExceptionListener as a Service, but with a higher priority than the default one.
my.exception_listener:
class: Acme\MyBundle\EventListener\ExceptionListener
arguments: [@security.context, @security.authentication.trust_resolver]
tags:
- { name: kernel.event_listener, event: kernel.exception, priority: 256 }
Upvotes: 0