How to store a JWT in a cookie and auto-refresh the token using LexikJWTAuthenticationBundle

Looking to store your JWT in a cookie and auto refresh it before it expires when using the LexikJWTAuthenticationBundle? There’s a setting that tells the bundle to look for a JWT cookie, but there’s nothing that actually creates the cookie for you. That needs to be done manually.

I’m going to assume if you’re here, you already have the bundle installed and are just looking for a way to store the JWT in a cookie, so I’m going to get straight to how you do it.

Update the LexikJWTAuthenticationBundle config

To tell the bundle that you want it to look for the JWT in a cookie, you’ll need to add the following to your lexik_jwt_authentication.yaml config file:

lexik_jwt_authentication:
   token_extractors:
       cookie:
           enabled: true
           name: BEARER

“BEARER” can be whatever you want, this is just the default value that’s given in the bundle’s documentation.

Adding the Set-Cookie Header

You’ll need to create an EventSubscriber that listens for the AUTHENTICATION_SUCCESS event. This event has a method called getData which returns an array with the token. It also has a getResponse method which you can use to set the cookie.

Here’s the code for the subscriber:

<?php
namespace App\EventSubscriber;

use Symfony\Component\HttpFoundation\Cookie;
use Lexik\Bundle\JWTAuthenticationBundle\Events;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent;

class JWTSubscriber implements EventSubscriberInterface
{
   public static function getSubscribedEvents()
   {
       return [
           Events::AUTHENTICATION_SUCCESS => 'onAuthenticationSuccess'
       ];
   }

   public function onAuthenticationSuccess(AuthenticationSuccessEvent $event)
   {
       $eventData = $event->getData();
       if(isset($eventData['token']))
       {
           $response = $event->getResponse();
           $jwt = $eventData['token'];

           // Set cookie
           $response->headers->setCookie(
               new Cookie(
                   "BEARER",
                   $jwt,
                   new \DateTime("+1 day"),
                   "/",
                   null,
                   true,
                   true,
                   false,
                   'strict'
               )
           );
       }
   }
}

You’ll need to change the name of the cookie (“BEARER”) to whatever you set in your config.

Auto-Refreshing the Token

Refreshing the JWT is done similarly, except this time we need to listen to a few more events. After you’ve authenticated by passing a valid JWT to your endpoint, the JWT_AUTHENTICATED event is dispatched. This event has the method getPayload which returns an array with the payload. You’ll want to check the exp key of the returned array to see if the token is about to expire. The event also has a getToken method which lets you get the user by calling the getUser method off of it ($event->getToken()->getUser()).

However, we won’t have access to the response object when listening for this event. So we need to listen for another event to refresh the token and cookie. For that we’ll listen for the KernelEvents::RESPONSE event. We’ll use the response object there to set the cookie.

Here’s the updated subscriber to allow for refreshing the token and saving it in a cookie:

<?php
namespace App\EventSubscriber;

use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\KernelEvents;
use Lexik\Bundle\JWTAuthenticationBundle\Events;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTAuthenticatedEvent;
use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;

class JWTSubscriber implements EventSubscriberInterface
{
   
   const REFRESH_TIME = 1800;

   private $payload;
   private $user;

   public function __construct(JWTTokenManagerInterface $jwtManager)
   {
       $this->jwtManager = $jwtManager;
   }

   public static function getSubscribedEvents()
   {
       return [
           Events::AUTHENTICATION_SUCCESS => 'onAuthenticationSuccess',
           Events::JWT_AUTHENTICATED => 'onAuthenticatedAccess',
           KernelEvents::RESPONSE => 'onAuthenticatedResponse'
       ];
   }

   public function onAuthenticatedResponse(FilterResponseEvent $event)
   {
       if($this->payload && $this->user)
       {
           $expireTime = $this->payload['exp'] - time();
           if($expireTime < static::REFRESH_TIME)
           {
               // Refresh token
               $jwt = $this->jwtManager->create($this->user);

               $response = $event->getResponse();

               // Set cookie
               $this->createCookie($response, $jwt);
           }
       }
   }

   public function onAuthenticatedAccess(JWTAuthenticatedEvent $event)
   {
       $this->payload = $event->getPayload();
       $this->user = $event->getToken()->getUser();
   }

   public function onAuthenticationSuccess(AuthenticationSuccessEvent $event)
   {
       $eventData = $event->getData();
       if(isset($eventData['token']))
       {
           $response = $event->getResponse();
           $jwt = $eventData['token'];

           // Set cookie
           $this->createCookie($response, $jwt);
       }
   }

   protected function createCookie(Response $response, $jwt)
   {
       $response->headers->setCookie(
           new Cookie(
               "BEARER",
               $jwt,
               new \DateTime("+1 day"),
               "/",
               null,
               false,
               true,
               false,
               'strict'
           )
       );
   }
}

Here we’re saving the user and payload to properties in the onAuthenticatedAccess method so we can use them once the response is about to be sent in the onAuthenticatedResponse method. Then before refreshing the token, we’re checking to see if the token is going to expire within the next 30 minutes. You can change this time to whatever you’d like.

The only other thing that I didn’t mention before is using the JWTTokenManagerInterface to create the JWT. That’s just how the bundle generates the JWT. I’ve also put the cookie creation in a method since we are creating a cookie in two different spots.


That’s pretty much it! Your JWT will now be stored in a cookie and refresh automatically. If you have any questions or feedback let me know in the comments below!