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]]; + } +}