I'm using symfony, lexik for generate tokens and gesdinet for refresh token. I've got problem with logout.
lexik_jwt_authentication:
secret_key: '%env(resolve:JWT_SECRET_KEY)%'
public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
pass_phrase: '%env(JWT_PASSPHRASE)%'
token_ttl: 300 # 5min
gesdinet_jwt_refresh_token:
refresh_token_class: App\Entity\RefreshToken # Scaffolded by the bundle recipe
ttl: 7200 # 2h in seconds
single_use: true
# Use cookies for the refresh token
cookie:
enabled: true
remove_token_from_body: true
# Cookie parameters
http_only: true
same_site: strict # tylko dla HTTPS
secure: true
path: /api/token
domain: null
symfony.yaml
security:
enable_authenticator_manager: true
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
login:
pattern: ^/api/login
stateless: true
json_login:
check_path: /api/login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
logout:
path: app_logout
csrf_token_generator: security.csrf.token_manager
refresh_token:
pattern: ^/api/token
stateless: true
refresh_jwt:
check_path: gesdinet_jwt_refresh_token
api:
pattern: ^/api
stateless: true
jwt: ~
access_control:
- { path: ^/api/login, roles: PUBLIC_ACCESS }
- { path: ^/api/(login|refresh), roles: PUBLIC_ACCESS }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
LogoutController:
use App\Entity\RefreshToken;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface as CsrfTokenStorageInterface;
class LogoutController extends AbstractController
{
public function __construct(
private RequestStack $requestStack,
private TokenStorageInterface $tokenStorage,
private CsrfTokenStorageInterface $csrfTokenStorage,
private EntityManagerInterface $entityManager
) {
}
#[Route('/api/logout', name: 'app_logout')]
public function logout(): Response
{
$token = $this->tokenStorage->getToken();
if ($token !== null) {
$this->tokenStorage->setToken(null);
}
$csrfTokenId = $this->requestStack->getCurrentRequest()->get('_csrf_token_id');
if ($csrfTokenId !== null) {
$this->csrfTokenStorage->removeToken($csrfTokenId);
}
$tokens = $this->entityManager->getRepository(RefreshToken::class)->findBy([
'username' => $token->getUserIdentifier()
]);
foreach ($tokens as $item) {
$this->entityManager->remove($item);
}
$this->entityManager->flush();
return new Response();
}
}
User entity:
#[ApiResource(
operations: [
new Get(),
new Get(name: 'current_user', uriTemplate: '/user'),
],
normalizationContext: ['groups' => ['api']],
denormalizationContext: ['groups' => ['api']]
)]
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
...
}
RefreshToken entity:
#[ORM\Entity]
#[ORM\Table(name: 'refresh_tokens')]
class RefreshToken extends BaseRefreshToken
{
}
Sooo. I made endpoint which get currect logged user and I pull it through a filter that uses entity operations.
final class CurrentUserExtension implements QueryItemExtensionInterface
{
public function __construct(private readonly TokenStorageInterface $tokenStorage)
{
}
public function applyToItem(
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
array $identifiers,
Operation $operation = null,
array $context = []
): void {
if (User::class === $resourceClass && 'current_user' === $operation->getName() && $this->tokenStorage->getToken()) {
$this->support($queryBuilder);
}
}
private function support(QueryBuilder $queryBuilder): void
{
$user = $this->tokenStorage->getToken()->getUserIdentifier();
$rootAlias = $queryBuilder->getRootAliases()[0];
$queryBuilder->andWhere(sprintf('%s.email = :current_user_email', $rootAlias));
$queryBuilder->setParameter('current_user_email', $user);
}
}
Logout works. I think so. But after sending a request to get the current user, I still get it, and I shouldn't because I'm logged out. In addition, I dumped the token in the extension, and it still exists, although it shouldn't. A new token is also added to the refresh_token table. What to do? How to live?
I dont think it is possible to revoke a JWT, as no database is used to store it, and it will live till it expires.
To revoke the refresh token you should execute :