diff --git a/src/EventListener/AccessDeniedListener.php b/src/EventListener/AccessDeniedListener.php index 096416cc..ab99027b 100644 --- a/src/EventListener/AccessDeniedListener.php +++ b/src/EventListener/AccessDeniedListener.php @@ -63,13 +63,19 @@ class AccessDeniedListener implements EventSubscriberInterface return; } - /** @var Session $session */ - $session = $request->getSession(); - $this->authLogger->warning('Access denied', [ 'uri' => $request->getRequestUri(), ]); + // API clients (/api, /mcp, webhooks) get the plain 401/403. A flash would start a + // session nobody reads and send a cookie, and a redirect would answer with HTML. + if (true === $this->security->getFirewallConfig($request)?->isStateless()) { + return; + } + + /** @var Session $session */ + $session = $request->getSession(); + if (null === $this->security->getUser()) { $session->getFlashBag()->add('info', 'Bitte melde dich an.'); diff --git a/tests/EventListener/AccessDeniedListenerTest.php b/tests/EventListener/AccessDeniedListenerTest.php index 9c3c30cc..1f0bb1ff 100644 --- a/tests/EventListener/AccessDeniedListenerTest.php +++ b/tests/EventListener/AccessDeniedListenerTest.php @@ -11,6 +11,7 @@ use App\Security\DefaultRouteResolver; use PHPUnit\Framework\TestCase; use Psr\Log\LoggerInterface; use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Bundle\SecurityBundle\Security\FirewallConfig; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\Session; @@ -83,6 +84,17 @@ class AccessDeniedListenerTest extends TestCase self::assertSame([], $this->session($request)->getFlashBag()->all()); } + public function testStatelessFirewallsGetThePlainResponseWithoutStartingASession(): void + { + foreach ([null, new User('client@example.org')] as $user) { + $request = $this->request('/mcp'); + $event = $this->dispatch($request, $user, stateless: true); + + self::assertFalse($event->hasResponse()); + self::assertFalse($this->session($request)->isStarted()); + } + } + /** * @param array $server */ @@ -102,10 +114,11 @@ class AccessDeniedListenerTest extends TestCase return $session; } - private function dispatch(Request $request, ?UserInterface $user): ExceptionEvent + private function dispatch(Request $request, ?UserInterface $user, bool $stateless = false): ExceptionEvent { $security = $this->createStub(Security::class); $security->method('getUser')->willReturn($user); + $security->method('getFirewallConfig')->willReturn(new FirewallConfig($stateless ? 'mcp' : 'main', 'security.user_checker', stateless: $stateless)); $resolver = $this->createStub(DefaultRouteResolver::class); $resolver->method('resolveUrl')->willReturn('/admin/dashboard');