Open mbeineris opened 6 years ago
Attempted to fix this in PR #197
Any news about your fix @Emroni ? or a workaround ?
@ayyouboulidi unfortunately not..
A workaround could be to fire a cronjob every night or so to delete all but the last of each user.
That's too bad but thanks for the answer @Emroni
Hi, @ayyouboulidi @Emroni you can overide the service :
` gesdinet.jwtrefreshtoken.send_token: class: App\EventListener\AttachRefreshTokenOnSuccessListener arguments: [ "@gesdinet.jwtrefreshtoken.refresh_token_manager", "%gesdinet_jwt_refresh_token.ttl%", "@validator", "@request_stack", "%gesdinet_jwt_refresh_token.user_identity_field%", "%gesdinet_jwt_refresh_token.token_parameter_name%", "%gesdinet_jwt_refresh_token.single_use%" ] tags:
`<?php
/*
namespace App\EventListener;
use Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenInterface; use Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenManagerInterface; use Gesdinet\JWTRefreshTokenBundle\Request\RequestRefreshToken; use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\PropertyAccess\PropertyAccessor;
class AttachRefreshTokenOnSuccessListener { /**
@var RefreshTokenManagerInterface */ protected $refreshTokenManager;
/**
@var int */ protected $ttl;
/**
@var ValidatorInterface */ protected $validator;
/**
@var RequestStack */ protected $requestStack;
/**
@var string */ protected $userIdentityField;
/**
@var string */ protected $tokenParameterName;
/**
@var bool */ protected $singleUse;
/**
@param bool $singleUse */ public function __construct( RefreshTokenManagerInterface $refreshTokenManager, int $ttl, ValidatorInterface $validator, RequestStack $requestStack, string $userIdentityField, string $tokenParameterName, bool $singleUse ) { $this->refreshTokenManager = $refreshTokenManager; $this->ttl = $ttl; $this->validator = $validator; $this->requestStack = $requestStack; $this->userIdentityField = $userIdentityField; $this->tokenParameterName = $tokenParameterName; $this->singleUse = $singleUse; }
public function attachRefreshToken(AuthenticationSuccessEvent $event) { $data = $event->getData(); $user = $event->getUser(); $request = $this->requestStack->getCurrentRequest();
if (!$user instanceof UserInterface) {
return;
}
$refreshTokenString = RequestRefreshToken::getRefreshToken($request, $this->tokenParameterName);
if ($refreshTokenString && true === $this->singleUse) {
$refreshToken = $this->refreshTokenManager->get($refreshTokenString);
$refreshTokenString = null;
if ($refreshToken instanceof RefreshTokenInterface) {
$this->refreshTokenManager->delete($refreshToken);
}
}
if ($refreshTokenString) {
$data[$this->tokenParameterName] = $refreshTokenString;
} else {
$datetime = new \DateTime();
$datetime->modify('+'.$this->ttl.' seconds');
$accessor = new PropertyAccessor();
$userIdentityFieldValue = $accessor->getValue($user, $this->userIdentityField);
$refreshToken = $this->refreshTokenManager->getLastFromUsername($userIdentityFieldValue);
if (!$refreshToken) {
$refreshToken = $this->refreshTokenManager->create();
$refreshToken->setUsername($userIdentityFieldValue);
}
$refreshToken->setRefreshToken();
$refreshToken->setValid($datetime);
$valid = false;
while (false === $valid) {
$valid = true;
$errors = $this->validator->validate($refreshToken);
if ($errors->count() > 0) {
foreach ($errors as $error) {
if ('refreshToken' === $error->getPropertyPath()) {
$valid = false;
$refreshToken->setRefreshToken();
}
}
}
}
$this->refreshTokenManager->save($refreshToken);
$data[$this->tokenParameterName] = $refreshToken->getRefreshToken();
}
$event->setData($data);
} }`
Couldn't this be problematic if the user is supposed to be able to login from several devices?
Option would be great but should be optional imo. :)
Hello everyone,
I managed to find a very fast forward solution for the issue ( without over engineering for our beginners ) :
config/services.yaml
parameters:
services:
App\EventListener\AuthenticationSuccessListener:
tags:
- { name: kernel.event_listener, event: lexik_jwt_authentication.on_authentication_success, method: onAuthenticationSuccessResponse }
src/EventListener/AuthenticationSuccessListener.php
<?php
namespace App\EventListener;
use App\Entity\AuthUser;
use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\RefreshToken;
class AuthenticationSuccessListener
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function onAuthenticationSuccessResponse(AuthenticationSuccessEvent $event)
{
$data = $event->getData();
$user = $event->getUser();
if (!$user instanceof AuthUser) {
return;
}
$username = $user->getUsername();
$refreshTokenRepository = $this->em->getRepository(RefreshToken::class);
$refreshTokens = $refreshTokenRepository->findBy(['username' => $username], ['valid' => 'DESC']);
array_shift($refreshTokens);
foreach ($refreshTokens as $refreshToken) {
$this->em->remove($refreshToken);
}
$this->em->flush();
$event->setData($data);
}
}
Hope this helped some of y'all 🤠
Perhaps I am missing something but Is there a reason why each login creates new db entry with new refresh token? Users end up with many tokens that are still valid. Perhaps on login have an option to remove old one or update existing one with new expiry date? Thanks in advance for shedding any light on my question.