Skip to content

Commit c1e91c5

Browse files
bug #62037 Fix generating logout link with stateless csrf (pierredup)
This PR was squashed before being merged into the 7.3 branch. Discussion ---------- Fix generating logout link with stateless csrf | Q | A | ------------- | --- | Branch? | 7.3 | Bug fix? | yes | New feature? | no | Deprecations? | no | Issues | N/A | License | MIT When using the `logout_path` (or `logout_url`) twig function with stateless csrf, the generator creates a link with an invalid CSRF token parameter (`/logout?_csrf_token=csrf-token`), which then causes an error during logout (`Invalid CSRF token`), since the LogoutListener reads the token from the query parameter first. Reproducer: ```yaml framework: csrf_protection: stateless_token_ids: ['logout'] ``` ```twig <form method="post" action="{{ logout_path() }}"> <input type="hidden" data-controller="csrf-protection" name="_csrf_token" value="{{ csrf_token('logout') }}"/> <button type="submit">Logout</button> </form> ``` Commits ------- 9e5e32b Fix generating logout link with stateless csrf
2 parents 03d8f2c + 9e5e32b commit c1e91c5

File tree

2 files changed

+45
-1
lines changed

2 files changed

+45
-1
lines changed

src/Symfony/Component/Security/Http/Firewall/LogoutListener.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public function authenticate(RequestEvent $event): void
6969
$request = $event->getRequest();
7070

7171
if (null !== $this->csrfTokenManager) {
72-
$csrfToken = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']);
72+
$csrfToken = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter'], $request->request->all());
7373

7474
if (!\is_string($csrfToken) || false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $csrfToken))) {
7575
throw new LogoutException('Invalid CSRF token.');

src/Symfony/Component/Security/Http/Tests/Firewall/LogoutListenerTest.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
2121
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
2222
use Symfony\Component\Security\Core\Exception\LogoutException;
23+
use Symfony\Component\Security\Csrf\CsrfToken;
2324
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
2425
use Symfony\Component\Security\Http\Event\LogoutEvent;
2526
use Symfony\Component\Security\Http\Firewall\LogoutListener;
@@ -88,6 +89,49 @@ public function testHandleMatchedPathWithCsrfValidation()
8889
$this->assertSame($response, $event->getResponse());
8990
}
9091

92+
public function testHandleMatchedPathWithCsrfInQueryParamAndBody()
93+
{
94+
$tokenManager = $this->getTokenManager();
95+
$dispatcher = $this->getEventDispatcher();
96+
97+
[$listener, $tokenStorage, $httpUtils, $options] = $this->getListener($dispatcher, $tokenManager);
98+
99+
$request = new Request();
100+
$request->query->set('_csrf_token', 'token');
101+
$request->request->set('_csrf_token', 'token2');
102+
103+
$httpUtils->expects($this->once())
104+
->method('checkRequestPath')
105+
->with($request, $options['logout_path'])
106+
->willReturn(true);
107+
108+
$tokenManager->expects($this->once())
109+
->method('isTokenValid')
110+
->with($this->callback(function ($token) {
111+
return $token instanceof CsrfToken && 'token2' === $token->getValue();
112+
}))
113+
->willReturn(true);
114+
115+
$response = new Response();
116+
$dispatcher->addListener(LogoutEvent::class, function (LogoutEvent $event) use ($response) {
117+
$event->setResponse($response);
118+
});
119+
120+
$tokenStorage->expects($this->once())
121+
->method('getToken')
122+
->willReturn($token = $this->getToken());
123+
124+
$tokenStorage->expects($this->once())
125+
->method('setToken')
126+
->with(null);
127+
128+
$event = new RequestEvent($this->createMock(HttpKernelInterface::class), $request, HttpKernelInterface::MAIN_REQUEST);
129+
130+
$listener($event);
131+
132+
$this->assertSame($response, $event->getResponse());
133+
}
134+
91135
public function testHandleMatchedPathWithoutCsrfValidation()
92136
{
93137
$dispatcher = $this->getEventDispatcher();

0 commit comments

Comments
 (0)