<?php
namespace App\EventListener;
use App\Security\Dto\TokensBag;
use App\Service\KeycloakApiService;
use Psr\Log\LoggerInterface;
use RuntimeException;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
class KeycloakRefreshTokenListener implements EventSubscriberInterface
{
private LoggerInterface $logger;
private UserProviderInterface $userProvider;
private TokenStorageInterface $tokenStorage;
private UrlGeneratorInterface $urlGenerator;
private KeycloakApiService $keycloakApiService;
private ParameterBagInterface $param;
public function __construct(
LoggerInterface $logger,
UserProviderInterface $userProvider,
TokenStorageInterface $tokenStorage,
UrlGeneratorInterface $urlGenerator,
KeycloakApiService $keycloakApiService,
ParameterBagInterface $param,
) {
$this->logger = $logger;
$this->userProvider = $userProvider;
$this->tokenStorage = $tokenStorage;
$this->urlGenerator = $urlGenerator;
$this->keycloakApiService = $keycloakApiService;
$this->param = $param;
}
public function onKernelRequest(RequestEvent $event): void
{
$request = $event->getRequest();
$session = $request->getSession();
$token = $this->tokenStorage->getToken();
if (null === $token) {
return;
}
$tokens = $token->getAttribute(TokensBag::class);
if (null === $tokens) {
throw new \LogicException(sprintf('%s token attribute is empty', TokensBag::class));
}
if (time() < $tokens->getJwtExpires()) {
return;
}
$refreshToken = $session->get('oidc_refresh_token');
try {
$response = $this->keycloakApiService->getTokenFromRefreshToken($refreshToken);
} catch (HttpExceptionInterface $e) {
$response = $e->getResponse();
if (400 === $response->getStatusCode() && 'invalid_grant' === ($response->toArray(false)['error'] ?? null)) {
// Logout when SSO session idle is reached
$this->tokenStorage->setToken(null);
$redirect = $this->urlGenerator->generate('login');
$event->setResponse(new RedirectResponse($redirect));
return;
}
throw new RuntimeException(
sprintf('Bad status code returned by openID server (%s)', $e->getResponse()->getStatusCode()),
previous: $e,
);
}
$responseData = json_decode($response, true);
if (false === $responseData) {
throw new RuntimeException(sprintf('Can\'t parse json in response: %s', $response));
}
$jwtToken = $responseData['id_token'] ?? null;
if (null === $jwtToken) {
throw new RuntimeException(sprintf('No access token found in response %s', $response));
}
$refreshToken = $responseData['refresh_token'] ?? null;
if (null === $refreshToken) {
throw new RuntimeException(sprintf('No refresh token found in response %s', $response));
}
$this->logger->info(">>>>>>>>>>>>>>>>>> DEBUT REFRESH TOKEN >>>>>>>>>>>>>>>>>>");
$this->logger->debug($responseData['refresh_token']);
$this->logger->info("<<<<<<<<<<<<<<<<<< FIN REFRESH TOKEN <<<<<<<<<<<<<<<<<<");
$session->remove('oidc_access_token');
$session->remove('oidc_refresh_token');
$session->remove('oidc_expires_token');
$session->remove('oidc_expires_refresh_token');
$session->set('oidc_access_token', $responseData['access_token']);
$session->set('oidc_refresh_token', $responseData['refresh_token']);
$session->set('oidc_expires_token', $responseData['expires_in']);
$session->set('oidc_expires_refresh_token', $responseData['refresh_expires_in']);
setcookie("celaneo_access_token", "", time() - 3600);
setcookie("celaneo_refresh_token", "", time() - 3600);
setcookie("celaneo_access_token", $responseData['access_token'], time() + $responseData['expires_in']);
setcookie("celaneo_refresh_token", $responseData['refresh_token'], time() + $responseData['refresh_expires_in']);
}
public static function getSubscribedEvents(): array
{
return [RequestEvent::class => 'onKernelRequest'];
}
}