-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Form] Add csrf_token_lazy
option
#54705
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@chalasr I've decided against implementing an internal controller action for AJAX requests. It's important to note that there's no missing code, as the CSRF token will still be validated by Symfony's FormTypeCsrfExtension in all cases. This mechanism ensures that token validation fails when the token field is an empty string on the form, thereby maintaining security standards. Instead, I suggest providing guidance for developers to determine whether to handle token generation internally or independently. This approach preserves flexibility while mitigating potential risks associated with token handling. I'm open to alternative suggestions regarding the implementation of lazy CSRF-token generation. If you have any ideas on how to address potential risks while maintaining flexibility, I'd love to hear them. Edit: Indeed, achieving lazy CSRF-token generation can be approached in various ways. For developers who prefer not to rely on JavaScript and AJAX requests, an alternative method could involve utilizing form submissions to initiate the required form. For instance, a "show form" button could trigger a form submission to start the required form with the CSRF token initialized. This provides developers with flexibility in choosing the method that best suits their preferences and project requirements. public function buildForm(FormBuilderInterface $builder, array $options): void
{
if($options['csrf_token_lazy']) {
$builder->add('show_form', SubmitType::class);
return;
}
$builder->add('field_name', TextType::class)->add('submit', SubmitType:class);
} |
csrf_token_lazy
option
I'm failing to see where the lazyness happens? Where do we generate the token now? To me, the only way to be compatible with caching is using the double-submit strategy, with JavaScript to generate the second submit... Let me close because this is incomplete and this stalled. |
I wrote the below token manager due to lack of this option that I provided in this pull request. Framework should not force me to write weird stuff. <?php
namespace App\Service;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManager;
class CsrfLazyTokenManager extends CsrfTokenManager
{
protected bool $lazy = false;
public function isLazy(): bool
{
return $this->lazy;
}
public function setLazy(bool $lazy): CsrfLazyTokenManager
{
$this->lazy = $lazy;
return $this;
}
public function getToken(string $tokenId): CsrfToken
{
if ($this->isLazy()) {
return new CsrfToken($tokenId, '');
}
return parent::getToken($tokenId);
}
} This is my request handler. #[Route('/contact', name: 'contact')]
public function indexAction(Request $request): Response
{
$user = $this->getUser();
$csrfTokenLazy = !$request->hasPreviousSession();
if ($csrfTokenLazy && $request->isMethod('POST')
&& $request->isXmlHttpRequest()
&& $request->request->has('token')) {
$response = new Response($this->csrfTokenManager->getToken('lazy_form_token')->getValue());
$response->headers->set('Content-Type', 'text/plain');
return $response;
}
$contact = new Contact();
$contact->setUser($user);
$this->csrfTokenManager->setLazy($csrfTokenLazy);
$form = $this->createForm(ContactFormType::class, $contact, [
'csrf_token_manager' => $this->csrfTokenManager,
'csrf_token_lazy' => $csrfTokenLazy,
'csrf_token_id' => 'lazy_form_token',
'attr' => ['id' => 'contact-form'],
'action' => $this->generateUrl('contact')
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->entityManager->persist($contact);
$this->entityManager->flush();
return $this->saveFormAndRedirect($contact);
}
return $this->render('contact/index.html.twig', [
'form' => $form->createView()
]);
} |
OK, so you do an AJAX request to get the token. Honestly, if you rely on JS to prevent CSRF requests, you'd better use the double submit strategy. |
Problem is not style of handing CSRF request. Problem is always start session when page have a form. This sends a session cookie and breaks working behavior of frontend cache proxies like Varnish. This is acceptable approach for static content pages that cacheable like blog posts / article pages with comment forms. Actual working behavior is not affected for users who have sessions. CSRF token request is only required on first touch of a user. |
…SRF protection (nicolas-grekas) This PR was merged into the 7.2 branch. Discussion ---------- [Security] Implement stateless headers/cookies-based CSRF protection | Q | A | ------------- | --- | Branch? | 7.2 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | #13464 | License | MIT #54705 made me think about our CSRF protection and I wrote the attached CSRF token manager to implement stateless headers/cookies-based validation. By defaults, the existing stateful manager is used. In order to leverage this new stateless manager, one needs to list the token ids that should be managed this way: ```yaml framework: csrf_protection: stateless_token_ids: [my_stateless_token_id] ``` * This CSRF token manager uses a combination of cookie and headers to validate non-persistent tokens. * * This manager is designed to be stateless and compatible with HTTP-caching. * * First, we validate the source of the request using the Origin/Referer headers. This relies * on the app being able to know its own target origin. Don't miss configuring your reverse proxy to * send the X-Forwarded-* / Forwarded headers if you're behind one. * * Then, we validate the request using a cookie and a CsrfToken. If the cookie is found, it should * contain the same value as the CsrfToken. A JavaScript snippet on the client side is responsible * for performing this double-submission. The token value should be regenerated on every request * using a cryptographically secure random generator. * * If either double-submit or Origin/Referer headers are missing, it typically indicates that * JavaScript is disabled on the client side, or that the JavaScript snippet was not properly * implemented, or that the Origin/Referer headers were filtered out. * * Requests lacking both double-submit and origin information are deemed insecure. * * When a session is found, a behavioral check is added to ensure that the validation method does not * downgrade from double-submit to origin checks. This prevents attackers from exploiting potentially * less secure validation methods once a more secure method has been confirmed as functional. * * On HTTPS connections, the cookie is prefixed with "__Host-" to prevent it from being forged on an * HTTP channel. On the JS side, the cookie should be set with samesite=strict to strengthen the CSRF * protection. The cookie is always cleared on the response to prevent any further use of the token. * * The $checkHeader argument allows the token to be checked in a header instead of or in addition to a * cookie. This makes it harder for an attacker to forge a request, though it may also pose challenges * when setting the header depending on the client-side framework in use. * * When a fallback CSRF token manager is provided, only tokens listed in the $tokenIds argument will be * managed by this manager. All other tokens will be delegated to the fallback manager. ``` Since it's stateless, end users won't loose their content if they take time to submit a form: even if the session is destroyed while they populate their form, remember-me will reconnect them and the form will be accepted. Recipe update at symfony/recipes#1337 Commits ------- 27d8a31 [Security] Implement stateless headers/cookies-based CSRF protection
Description
This pull request introduces
csrf_token_lazy
, a new form option in Symfony. This option allows for deferred CSRF token generation until form submission, aiding in performance optimization and mitigating conflicts with caching mechanisms.Motivation
The necessity for this option arises from the default behavior of Symfony's CSRF protection mechanism, which initiates sessions for token generation. However, in scenarios where caching is employed, this behavior can lead to cache misses or bypasses, impacting application performance adversely. By introducing
csrf_token_lazy
, developers gain the ability to generate tokens only when necessary, thus avoiding unnecessary session starts during form rendering.Functionality
With
csrf_token_lazy
enabled, token generation occurs exclusively during form submission. This ensures that sessions are not initiated unnecessarily during form rendering, thereby maintaining security while optimizing performance, particularly in environments sensitive to caching concerns.Usage
Developers can enable
csrf_token_lazy
in form configurations: