Part 3. Refreshing the token

After we’ve done Part 1 and Part 2 parts — we can set the httpOnly cookie with an auth token and read it back authenticating the user. But what will happened when the token become outdated?
The next thing we need to think about — is to constantly refreshing the token while the user is using the app.
To do so — we can create a subscriber which should listen to 2 events: the first one is fired when the user is got authenticated and the second one is response event.
We need a file like this to be created in you subscribers dir:
<?phpnamespace App\EventSubscriber;use ApiPlatform\Core\EventListener\EventPriorities;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Lexik\Bundle\JWTAuthenticationBundle\Events; use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTAuthenticatedEvent; use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
class AuthSubscriber implements EventSubscriberInterface {const TIME_LEFT = 5 * 60;/** @var ParameterBagInterface */
private $params;/** @var JWTTokenManagerInterface */
private $jwtManager;/**
* @required
* @param ParameterBagInterface $params
*/
public function setUp(
ParameterBagInterface $params,
JWTTokenManagerInterface $jwtManager
) {
$this->params = $params;
$this->jwtManager = $jwtManager;
}public static function getSubscribedEvents(): array {
return [
Events::JWT_AUTHENTICATED => 'onAuthenticatedAccess',
KernelEvents::RESPONSE => ['onAuthenticatedResponse', EventPriorities::PRE_RESPOND]
];
}public function onAuthenticatedResponse(ResponseEvent $event) {
$response = $event->getResponse();
$response->headers->set('Access-Control-Allow-Credentials', 'true');
$response->headers->set('Access-Control-Allow-Headers', implode(', ', ['Content-Type', 'Origin']));
// you can add more custom headers (if you send some) hereif (!empty($this->payload) && !empty($this->user)) {
$expireTime = $this->payload['exp'] - time();
if ($expireTime < static::TIME_LEFT) {
// Refresh token by creating a new one
$jwt = $this->jwtManager->create($this->user);
// Set cookie
$this->setJWTCookie($response, $jwt);
}
}
}public function onAuthenticatedAccess(JWTAuthenticatedEvent $event) {
$this->payload = $event->getPayload();
$this->user = $event->getToken()->getUser();
}private function setJWTCookie(Response $response, $token) {
$ttlSeconds = $this->params->get('token_ttl');
$response->headers->setCookie(
Cookie::create(
"BEARER",
$token,
new \DateTime("+{$ttlSeconds} second"),
"/",
null,
true,
true,
false,
"lax"
)
);
}
}Some explanations here: to be fully in sync with our configuration and not to hardcode it twice — you have to move the lifetime of you token into a separate parameter and use it to configure lexik bundle and our httpOnly cookies.
You can use parameters.yml located in /config/packages/parameters.yaml in your project as follows:
parameters:
# some more params can live here
token_ttl: 3600And then in lexik config file (/config/packages/lexik_jwt_authentication.yaml) use it like this:
lexik_jwt_authentication:
token_ttl: '%token_ttl%'What have we done using the subscriber we created?
- When user got authenticated (Events::JWT_AUTHENTICATED) we run onAuthenticatedAccess method where the payload (token data) and the user is saved for the future.
- Using previously saved token data and user — we are checking if it’s time to refresh the credentials by comparing the time left to expiration with our own custom constant: TIME_LEFT.
- Finally (if it’s time to) we create a new jwt token and instruct the FE to save it for future requests by setting Set-Cookie header in setJWTCookie subscriber’s method.
The step #3 will be done over and over again until the user stops using app — then the auth token will just expire and the user will be forced to authenticate again.





