Last active
May 19, 2021 19:57
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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(); | |
} | |
} | |
} |
To accomplish the JWT invalidation, you are forced to hit the DB every time you decode a token right?
@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
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 }