diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.xml
index 861c606f5f..a09c04ea5b 100644
--- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.xml
+++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security_authenticator.xml
@@ -64,6 +64,11 @@
stateless firewall keys
+
+
+
+
+
diff --git a/src/Symfony/Component/Security/Http/Authenticator/CsrfProtectedAuthenticatorInterface.php b/src/Symfony/Component/Security/Http/Authenticator/CsrfProtectedAuthenticatorInterface.php
new file mode 100644
index 0000000000..0f93ad1e86
--- /dev/null
+++ b/src/Symfony/Component/Security/Http/Authenticator/CsrfProtectedAuthenticatorInterface.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Http\Authenticator;
+
+/**
+ * This interface can be implemented to automatically add CSF
+ * protection to the authenticator.
+ *
+ * @author Wouter de Jong
+ */
+interface CsrfProtectedAuthenticatorInterface
+{
+ /**
+ * An arbitrary string used to generate the value of the CSRF token.
+ * Using a different string for each authenticator improves its security.
+ */
+ public function getCsrfTokenId(): string;
+
+ /**
+ * Returns the CSRF token contained in credentials if any.
+ *
+ * @param mixed $credentials the credentials returned by getCredentials()
+ */
+ public function getCsrfToken($credentials): ?string;
+}
diff --git a/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php b/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php
index 75bac9bd90..2ec3792a7c 100644
--- a/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php
+++ b/src/Symfony/Component/Security/Http/Authenticator/FormLoginAuthenticator.php
@@ -32,7 +32,7 @@ use Symfony\Component\Security\Http\Util\TargetPathTrait;
* @final
* @experimental in 5.1
*/
-class FormLoginAuthenticator extends AbstractLoginFormAuthenticator implements PasswordAuthenticatedInterface
+class FormLoginAuthenticator extends AbstractLoginFormAuthenticator implements PasswordAuthenticatedInterface, CsrfProtectedAuthenticatorInterface
{
use TargetPathTrait;
@@ -113,17 +113,15 @@ class FormLoginAuthenticator extends AbstractLoginFormAuthenticator implements P
return $this->userProvider->loadUserByUsername($credentials['username']);
}
- /* @todo How to do CSRF protection?
- public function checkCredentials($credentials, UserInterface $user): bool
+ public function getCsrfTokenId(): string
{
- if (null !== $this->csrfTokenManager) {
- if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['csrf_token_id'], $credentials['csrf_token']))) {
- throw new InvalidCsrfTokenException('Invalid CSRF token.');
- }
- }
+ return $this->options['csrf_token_id'];
+ }
- return $this->checkPassword($credentials, $user);
- }*/
+ public function getCsrfToken($credentials): ?string
+ {
+ return $credentials['csrf_token'];
+ }
public function createAuthenticatedToken(UserInterface $user, $providerKey): TokenInterface
{
diff --git a/src/Symfony/Component/Security/Http/EventListener/CsrfProtectionListener.php b/src/Symfony/Component/Security/Http/EventListener/CsrfProtectionListener.php
new file mode 100644
index 0000000000..fcde792452
--- /dev/null
+++ b/src/Symfony/Component/Security/Http/EventListener/CsrfProtectionListener.php
@@ -0,0 +1,52 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Http\EventListener;
+
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
+use Symfony\Component\Security\Csrf\CsrfToken;
+use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
+use Symfony\Component\Security\Http\Authenticator\CsrfProtectedAuthenticatorInterface;
+use Symfony\Component\Security\Http\Event\VerifyAuthenticatorCredentialsEvent;
+
+class CsrfProtectionListener implements EventSubscriberInterface
+{
+ private $csrfTokenManager;
+
+ public function __construct(CsrfTokenManagerInterface $csrfTokenManager)
+ {
+ $this->csrfTokenManager = $csrfTokenManager;
+ }
+
+ public function verifyCredentials(VerifyAuthenticatorCredentialsEvent $event): void
+ {
+ $authenticator = $event->getAuthenticator();
+ if (!$authenticator instanceof CsrfProtectedAuthenticatorInterface) {
+ return;
+ }
+
+ $csrfTokenValue = $authenticator->getCsrfToken($event->getCredentials());
+ if (null === $csrfTokenValue) {
+ return;
+ }
+
+ $csrfToken = new CsrfToken($authenticator->getCsrfTokenId(), $csrfTokenValue);
+ if (false === $this->csrfTokenManager->isTokenValid($csrfToken)) {
+ throw new InvalidCsrfTokenException('Invalid CSRF token.');
+ }
+ }
+
+ public static function getSubscribedEvents(): array
+ {
+ return [VerifyAuthenticatorCredentialsEvent::class => ['verifyCredentials', 256]];
+ }
+}