Skip to content

Instantly share code, notes, and snippets.

@benjaminrau
Last active May 19, 2021 19:57
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save benjaminrau/865f94d142605eb72a23a34ccdd0617a to your computer and use it in GitHub Desktop.
Save benjaminrau/865f94d142605eb72a23a34ccdd0617a to your computer and use it in GitHub Desktop.
Mechanism to invalidate JWT for specific User in Symfony with Lexik JWT Bundle
<?php
namespace Emma\UserBundle\EventListener;
use Doctrine\ORM\EntityManagerInterface;
use Emma\UserBundle\Entity\User;
use FOS\UserBundle\Event\FormEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use FOS\UserBundle\FOSUserEvents;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
class InvalidateTokenEventListener implements EventSubscriberInterface {
/**
* @var EntityManagerInterface
*/
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* @return array
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::RESETTING_RESET_SUCCESS => 'invalidateToken',
FOSUserEvents::CHANGE_PASSWORD_SUCCESS => 'invalidateToken',
);
}
/**
* @param FormEvent $event
*/
public function invalidateToken(FormEvent $event)
{
/** @var User $user */
$user = $event->getForm()->getData();
$user->setTokenValidAfter(new \DateTime());
$this->entityManager->persist($user);
$this->entityManager->flush();
}
}
<?php
namespace Emma\AppBundle\EventListener;
use Doctrine\ORM\EntityManagerInterface;
use Emma\UserBundle\Entity\User;
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTDecodedEvent;
class JWTDecodedEventListener
{
/**
* @var EntityManagerInterface
*/
private $entityManager;
/**
* @param EntityManagerInterface $entityManager
*/
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* @param JWTDecodedEvent $event
*
* @throws \Exception
*
* @return void
*/
public function onJWTDecoded(JWTDecodedEvent $event)
{
$payload = $event->getPayload();
/**
* As a mechanism to invalidate issued tokes we force token issue date to be higher than a date stored on User::tokenValidAfter
* By updating the User::tokenValidAfter to current date all previously issued tokens become invalid
*
* Its intended we dont mark as invalid if user isnt found on persistence level because we rely on core JWT
* implementation to handle this case. We want to handle only the validation of tokenValidAfter here.
*
* @var $user User
*/
$user = $this->entityManager->getRepository(User::class)->findOneBy([
'username' => $payload[JWTCreatedEventListener::PAYLOAD_KEY_FOR_USERNAME]
]);
if (
$user &&
$user->getTokenValidAfter() instanceof \DateTime &&
$payload['iat'] < $user->getTokenValidAfter()->getTimestamp()
) {
$event->markAsInvalid();
}
}
}
@AbdaliDahir
Copy link

Thanks for this. it's helpful i based on it to make something for now.
if someone interested don't miss to add service too.
acme_api.event.jwt_decoded_listener:
class: App\EventListener\JWTDecodedEventListener
tags:
- { name: kernel.event_listener, event: lexik_jwt_authentication.on_jwt_decoded, method: onJWTDecoded }

@ovidals
Copy link

ovidals commented Apr 22, 2021

To accomplish the JWT invalidation, you are forced to hit the DB every time you decode a token right?

@benjaminrau

@benjaminrau
Copy link
Author

@ovidals Indeed. I expected that to be the case with Symfonys default config anyway. Am i wrong?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment