symfony: [5.3 beta2][Security] Can't validate manual CSRF (test env only)
Symfony version(s) affected: 5.3 beta2
Description
A csrf token put inside a twig template with the csrf_token() helper can’t be validated any-more. This only occurs when using the test environment (PHPUnit 7.5.20) .
How to reproduce
Twig :
<input type="hidden" name="_token" value="{{ csrf_token('delete'~event.id) }}">
Controller :
if ($this->isCsrfTokenValid('delete'.$event->getId(), $request->request->get('_token'))) {
Test :
$csrfToken = self::getContainer()->get('security.csrf.token_manager')->getToken('delete1')->getValue();
echo $csrfToken;
$client->request('DELETE', '/event/delete/1', [
'_token' => $csrfToken,
]);
Possible Solution Seems related to the test environment as everything works correctly in dev or prod.
Additional context
- Everything was OK with 5.2.6.
- The problem occurs everywhere
csrf_token(is used in a template
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Comments: 15 (5 by maintainers)
Unorthodoxly solved by:
Dummy POC implementation now is:
That was one of my previous leads, thanks for posting your whole solution. Pretty sure it always throws in
ensureSessionIsAvailable(), so you always get the session fromself::getContainer()->get('session'). Couldn’t make it work in my API Calls though, as it seems to unlog the user, but I haven’t tested it thoroughly.@Pixelshaped I did find a possible work-around. The issue with the CSRF token not being stored to the session seemed to stem from the fact that there wasn’t a logged-in session. Since the Session service is being deprecated in favor of getting the RequestStack, then calling to get the Session from the RequestStack. The session from the RequestStack was simply the Session instance from the last Request object in the stack.
I will give one caveat to this, I haven’t tested if this works with following redirects or multiple request inside a single test case.
I added this to my abstract
WebTestCasethat extends from Symfony’sWebTestCase.So, when calling
$this->getCsrfToken('<token id>')from inside a test, it checks to see if a Session is already available. If there is, all-good. If not, we push a blank request to the stack and set a session instance on it.I can confirm that this worked for my use-case where I just needed to get a single CSRF token for the login form. Whether it would work in a more-complex scenario, I’m not sure.
@Pixelshaped The code behind the route isn’t being tested. In our code, we mock the implementations and test only at the service layer. The test in question is the actual login form submission, so no user has been logged-in yet. I did end up using your example. However, having to make a test-only controller to generate the CSRF token instead of simply getting the CsrfTokenManager and generating the token from it directly in the test is a very… crude and hackish way to get the token.
I understand the reasoning for the token-storage changes and I’m not arguing against it. I’m asking for a more-elegant solution given that, even for basic form validation, I shouldn’t have to execute a request to create the form to test the submission logic behind it. This is grossly bloated and simply makes testing even more complicated and brittle. I should be able to run a test to simply test whether data being submitted is valid and not have to completely render a form, populate the form data, then use the crawler to “click” the submit button. I should be able to issue the data directly to the endpoint.