src/EventSubscriber/SignedCookieSubscriber.php line 50

Open in your IDE?
  1. <?php
  2. namespace App\EventSubscriber;
  3. use Aws\CloudFront\CookieSigner;
  4. use Psr\Log\LoggerInterface;
  5. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  6. use Symfony\Component\HttpFoundation\Cookie;
  7. use Symfony\Component\HttpFoundation\RequestStack;
  8. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  9. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  10. use Symfony\Component\HttpKernel\KernelEvents;
  11. use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
  12. use Symfony\Component\Security\Http\SecurityEvents;
  13. class SignedCookieSubscriber implements EventSubscriberInterface
  14. {
  15.     private const SET_COOKIES 'signed_cookie_auth_success';
  16.     /**
  17.      * @var \Symfony\Component\HttpFoundation\Session\SessionInterface
  18.      */
  19.     private $session;
  20.     /**
  21.      * @var \Psr\Log\LoggerInterface
  22.      */
  23.     private $logger;
  24.     /**
  25.      * @var string
  26.      */
  27.     private $privateKey;
  28.     /**
  29.      * @var string
  30.      */
  31.     private $pairId;
  32.     /**
  33.      * @var int
  34.      */
  35.     private $lifetime;
  36.     /**
  37.      * @var \Symfony\Component\HttpFoundation\Request
  38.      */
  39.     private $masterRequest;
  40.     public function __construct(
  41.         SessionInterface $session,
  42.         LoggerInterface $logger,
  43.         RequestStack $requestStack,
  44.         string $cloudFrontPrivateKey,
  45.         string $cloudFrontKeyPairId,
  46.         int $sessionLifetime
  47.     ) {
  48.         $this->session $session;
  49.         $this->logger $logger;
  50.         $this->privateKey $cloudFrontPrivateKey;
  51.         $this->pairId $cloudFrontKeyPairId;
  52.         $this->lifetime $sessionLifetime;
  53.         $this->masterRequest $requestStack->getMasterRequest();
  54.     }
  55.     public static function getSubscribedEvents(): array
  56.     {
  57.         return [
  58.             SecurityEvents::INTERACTIVE_LOGIN => [['setAuthSuccessFlag'10]],
  59.             KernelEvents::RESPONSE => [['setResponseCookies'10]],
  60.         ];
  61.     }
  62.     /**
  63.      * Set a flag in the session to indicate that signed cookies should be set.
  64.      *
  65.      * @param \Symfony\Component\Security\Http\Event\InteractiveLoginEvent $event
  66.      *   Kernel event object
  67.      */
  68.     public function setAuthSuccessFlag(InteractiveLoginEvent $event): void
  69.     {
  70.         // Disable signed cookie functionality if the config is empty.
  71.         if (empty($this->privateKey) && empty($this->pairId)) {
  72.             return;
  73.         }
  74.         $this->session->set(self::SET_COOKIEStrue);
  75.     }
  76.     /**
  77.      * Adds the necessary cookies for accessing assets through CloudFront.
  78.      *
  79.      * Note: Cookies are removed on logout by \App\Security\LogoutSuccessHandler
  80.      *
  81.      * @param \Symfony\Component\HttpKernel\Event\ResponseEvent $event
  82.      *   Kernel event object
  83.      */
  84.     public function setResponseCookies(ResponseEvent $event): void
  85.     {
  86.         // Set CloudFront signed cookies if a login event is detected.
  87.         if (!$this->session->get(self::SET_COOKIES)) {
  88.             return;
  89.         }
  90.         try {
  91.             foreach ($this->getSignedCookies() as $signedCookie) {
  92.                 $event->getResponse()->headers->setCookie($signedCookie);
  93.             }
  94.         } catch (\Exception $e) {
  95.             $this->logger->error('Could not set signed cookies.', ['exception' => $e]);
  96.         }
  97.         $this->session->remove(self::SET_COOKIES);
  98.     }
  99.     /**
  100.      * Creates signed cookies with a custom policy for all resources.
  101.      *
  102.      * @return Cookie[]
  103.      *   Array of Cookie objects ready to be added to a Response
  104.      */
  105.     private function getSignedCookies(): array
  106.     {
  107.         // Create a cookie that allows access to all assets in the S3 bucket through a CloudFront distribution.
  108.         // More about custom policies can be found here
  109.         // https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-setting-signed-cookie-custom-policy.html
  110.         $signer = new CookieSigner($this->pairId$this->privateKey);
  111.         $expire time() + $this->lifetime;
  112.         $policy json_encode([
  113.             'Statement' => [
  114.                 [
  115.                     'Condition' => [
  116.                         'DateLessThan' => ['AWS:EpochTime' => $expire],
  117.                     ],
  118.                 ],
  119.             ],
  120.         ], JSON_UNESCAPED_SLASHES JSON_THROW_ON_ERROR);
  121.         $domain $this->masterRequest->getHost();
  122.         $cookies = [];
  123.         foreach ($signer->getSignedCookie(nullnull$policy) as $name => $value) {
  124.             $cookies[] = Cookie::create($name$value$expire'/'$domain);
  125.         }
  126.         return $cookies;
  127.     }
  128. }