diff --git a/Controller/AuthorizeController.php b/Controller/AuthorizeController.php
index 69c10101..1bf64a79 100644
--- a/Controller/AuthorizeController.php
+++ b/Controller/AuthorizeController.php
@@ -30,7 +30,10 @@
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
+use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\User\UserInterface;
+use Symfony\Component\Security\Csrf\CsrfToken;
+use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Twig\Environment as TwigEnvironment;
/**
@@ -95,6 +98,11 @@ class AuthorizeController
*/
private $eventDispatcher;
+ /**
+ * @var CsrfTokenManagerInterface
+ */
+ private $csrfTokenManager;
+
/**
* This controller had been made as a service due to support symfony 4 where all* services are private by default.
* Thus, this is considered a bad practice to fetch services directly from container.
@@ -113,6 +121,7 @@ public function __construct(
ClientManagerInterface $clientManager,
EventDispatcherInterface $eventDispatcher,
TwigEnvironment $twig,
+ CsrfTokenManagerInterface $csrfTokenManager,
SessionInterface $session = null
) {
$this->requestStack = $requestStack;
@@ -124,6 +133,7 @@ public function __construct(
$this->router = $router;
$this->clientManager = $clientManager;
$this->eventDispatcher = $eventDispatcher;
+ $this->csrfTokenManager = $csrfTokenManager;
$this->twig = $twig;
}
@@ -134,17 +144,21 @@ public function authorizeAction(Request $request)
{
$user = $this->tokenStorage->getToken()->getUser();
+ $form = $this->authorizeForm;
+ $formHandler = $this->authorizeFormHandler;
+
if (!$user instanceof UserInterface) {
throw new AccessDeniedException('This user does not have access to this section.');
}
if ($this->session && true === $this->session->get('_fos_oauth_server.ensure_logout')) {
+ $this->checkCsrfTokenBeforeInvalidingTheSession($form, $request);
+
$this->session->invalidate(600);
$this->session->set('_fos_oauth_server.ensure_logout', true);
- }
- $form = $this->authorizeForm;
- $formHandler = $this->authorizeFormHandler;
+ $this->regenerateTokenForInvalidatedSession($form, $request);
+ }
/** @var PreAuthorizationEvent $event */
$event = $this->eventDispatcher->dispatch(new PreAuthorizationEvent($user, $this->getClient()));
@@ -216,7 +230,7 @@ protected function getClient()
if (null === $clientId = $request->get('client_id')) {
$formData = $request->get($this->authorizeForm->getName(), []);
- $clientId = isset($formData['client_id']) ? $formData['client_id'] : null;
+ $clientId = $formData['client_id'] ?? null;
}
$this->client = $this->clientManager->findClientByPublicId($clientId);
@@ -247,4 +261,67 @@ private function getCurrentRequest()
return $request;
}
+
+ /**
+ * Validate if the current POST CSRF token is valid.
+ * We need to do this now as the session will be regenerated due to the `ensure_logout` parameter.
+ */
+ private function checkCsrfTokenBeforeInvalidingTheSession(Form $form, Request $request): void
+ {
+ if (!$request->isMethod('POST')) {
+ // no need to check the CSRF token if we are not on a POST request (ie. submitting the form)
+ return;
+ }
+
+ if (!$form->getConfig()->getOption('csrf_protection')) {
+ // no csrf security, no need to validate token
+ return;
+ }
+
+ $tokenFieldName = $form->getConfig()->getOption('csrf_field_name');
+ $tokenId = $form->getConfig()->getOption('csrf_token_id') ?? $form->getName();
+
+ $formData = $request->request->get($form->getName());
+ $tokenValue = $formData[$tokenFieldName] ?? null;
+
+ $token = new CsrfToken($tokenId, $tokenValue);
+
+ if (!$this->csrfTokenManager->isTokenValid($token)) {
+ throw new InvalidCsrfTokenException();
+ }
+ }
+
+ /**
+ * This method will inject a newly regenerated CSRF token into the actual form
+ * as Symfony's form manager will check this token upon the current session.
+ *
+ * As we have regenerate a session, we need to inject the newly generated token into
+ * the form data.
+ *
+ * It does bypass Symfony form CSRF protection, but the CSRF token is validated
+ * in the `checkCsrfTokenBeforeInvalidingTheSession` method
+ */
+ private function regenerateTokenForInvalidatedSession(Form $form, Request $request): void
+ {
+ if (!$request->isMethod('POST')) {
+ // no need to check the CSRF token if we are not on a POST request (ie. submitting the form)
+ return;
+ }
+
+ if (!$form->getConfig()->getOption('csrf_protection')) {
+ // no csrf security, no need to regenerate a valid token
+ return;
+ }
+
+ $tokenFieldName = $form->getConfig()->getOption('csrf_field_name');
+ $tokenId = $form->getConfig()->getOption('csrf_token_id') ?? $form->getName();
+
+ // regenerate a new token and replace the form data as Symfony's form manager will check this token.
+ // the request token has already been checked.
+ $newToken = $this->csrfTokenManager->refreshToken($tokenId);
+
+ $formData = $request->request->get($form->getName());
+ $formData[$tokenFieldName] = $newToken->getValue();
+ $request->request->set($form->getName(), $formData);
+ }
}
diff --git a/Resources/config/authorize.xml b/Resources/config/authorize.xml
index 64f8204b..d4b2b7b9 100644
--- a/Resources/config/authorize.xml
+++ b/Resources/config/authorize.xml
@@ -33,6 +33,7 @@
+