vendor/gesdinet/jwt-refresh-token-bundle/Security/Http/Authenticator/RefreshTokenAuthenticator.php line 42

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of the GesdinetJWTRefreshTokenBundle package.
  4. *
  5. * (c) Gesdinet <http://www.gesdinet.com/>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Gesdinet\JWTRefreshTokenBundle\Security\Http\Authenticator;
  11. use DateTime;
  12. use Gesdinet\JWTRefreshTokenBundle\Event\RefreshTokenNotFoundEvent;
  13. use Gesdinet\JWTRefreshTokenBundle\Http\RefreshAuthenticationFailureResponse;
  14. use Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenInterface;
  15. use Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenManagerInterface;
  16. use Gesdinet\JWTRefreshTokenBundle\Request\Extractor\ExtractorInterface;
  17. use Gesdinet\JWTRefreshTokenBundle\Security\Exception\InvalidTokenException;
  18. use Gesdinet\JWTRefreshTokenBundle\Security\Exception\MissingTokenException;
  19. use Gesdinet\JWTRefreshTokenBundle\Security\Exception\TokenNotFoundException;
  20. use Gesdinet\JWTRefreshTokenBundle\Security\Http\Authenticator\Token\PostRefreshTokenAuthenticationToken;
  21. use Symfony\Component\HttpFoundation\Request;
  22. use Symfony\Component\HttpFoundation\Response;
  23. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  24. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  25. use Symfony\Component\Security\Core\Exception\LogicException;
  26. use Symfony\Component\Security\Core\User\UserProviderInterface;
  27. use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
  28. use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
  29. use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
  30. use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
  31. use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
  32. use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
  33. use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
  34. use Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface;
  35. use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
  36. use Symfony\Component\Security\Http\HttpUtils;
  37. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  38. class RefreshTokenAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface
  39. {
  40. private RefreshTokenManagerInterface $refreshTokenManager;
  41. private EventDispatcherInterface $eventDispatcher;
  42. private ExtractorInterface $extractor;
  43. private UserProviderInterface $userProvider;
  44. private AuthenticationSuccessHandlerInterface $successHandler;
  45. private AuthenticationFailureHandlerInterface $failureHandler;
  46. private array $options;
  47. private ?HttpUtils $httpUtils;
  48. public function __construct(
  49. RefreshTokenManagerInterface $refreshTokenManager,
  50. EventDispatcherInterface $eventDispatcher,
  51. ExtractorInterface $extractor,
  52. UserProviderInterface $userProvider,
  53. AuthenticationSuccessHandlerInterface $successHandler,
  54. AuthenticationFailureHandlerInterface $failureHandler,
  55. array $options,
  56. ?HttpUtils $httpUtils = null
  57. ) {
  58. $this->refreshTokenManager = $refreshTokenManager;
  59. $this->eventDispatcher = $eventDispatcher;
  60. $this->extractor = $extractor;
  61. $this->userProvider = $userProvider;
  62. $this->successHandler = $successHandler;
  63. $this->failureHandler = $failureHandler;
  64. $this->options = array_merge([
  65. 'check_path' => null, // @todo in 2.0, change the default to `/login_check`
  66. 'ttl' => 2592000,
  67. 'ttl_update' => false,
  68. 'token_parameter_name' => 'refresh_token',
  69. ], $options);
  70. $this->httpUtils = $httpUtils;
  71. if (null === $httpUtils) {
  72. trigger_deprecation('gesdinet/jwt-refresh-token-bundle', '1.1', 'Not passing an instance of "%s" to the "%s" constructor is deprecated, it will be required in 2.0.', HttpUtils::class, self::class);
  73. }
  74. }
  75. public function supports(Request $request): bool
  76. {
  77. if (null !== $this->httpUtils && null !== $this->options['check_path']) {
  78. return $this->httpUtils->checkRequestPath($request, $this->options['check_path']);
  79. }
  80. trigger_deprecation('gesdinet/jwt-refresh-token-bundle', '1.1', 'Checking if the refresh token is in the request in %s() is deprecated, as of 2.0 only the request path will be checked.', __METHOD__);
  81. return null !== $this->extractor->getRefreshToken($request, $this->options['token_parameter_name']);
  82. }
  83. public function authenticate(Request $request): Passport
  84. {
  85. $token = $this->extractor->getRefreshToken($request, $this->options['token_parameter_name']);
  86. if (null === $token) {
  87. throw new MissingTokenException();
  88. }
  89. $refreshToken = $this->refreshTokenManager->get($token);
  90. if (null === $refreshToken) {
  91. throw new TokenNotFoundException();
  92. }
  93. if (!$refreshToken->isValid()) {
  94. throw new InvalidTokenException(sprintf('Refresh token "%s" is invalid.', $refreshToken->getRefreshToken()));
  95. }
  96. if ($this->options['ttl_update']) {
  97. $expirationDate = new DateTime();
  98. // Explicitly check for a negative number based on a behavior change in PHP 8.2, see https://github.com/php/php-src/issues/9950
  99. if ($this->options['ttl'] > 0) {
  100. $expirationDate->modify(sprintf('+%d seconds', $this->options['ttl']));
  101. } elseif ($this->options['ttl'] < 0) {
  102. $expirationDate->modify(sprintf('%d seconds', $this->options['ttl']));
  103. }
  104. $refreshToken->setValid($expirationDate);
  105. $this->refreshTokenManager->save($refreshToken);
  106. }
  107. $method = method_exists($this->userProvider, 'loadUserByIdentifier') ? 'loadUserByIdentifier' : 'loadUserByUsername';
  108. $passport = new SelfValidatingPassport(new UserBadge($refreshToken->getUsername(), [$this->userProvider, $method]));
  109. $passport->setAttribute('refreshToken', $refreshToken);
  110. return $passport;
  111. }
  112. /**
  113. * @deprecated to be removed in 2.0, use `createToken()` instead
  114. */
  115. public function createAuthenticatedToken(PassportInterface $passport, string $firewallName): TokenInterface
  116. {
  117. if (!$passport instanceof UserPassportInterface) {
  118. throw new LogicException(sprintf('Passport does not contain a user, overwrite "createToken()" in "%s" to create a custom authentication token.', static::class));
  119. }
  120. trigger_deprecation('gesdinet/jwt-refresh-token-bundle', '1.0', '"%s()" is deprecated, use "%s::createToken()" instead.', __METHOD__, __CLASS__);
  121. return $this->createToken($passport, $firewallName);
  122. }
  123. public function createToken(Passport $passport, string $firewallName): TokenInterface
  124. {
  125. /** @var RefreshTokenInterface|null $refreshToken */
  126. $refreshToken = $passport->getAttribute('refreshToken');
  127. if (null === $refreshToken) {
  128. throw new LogicException('Passport does not contain the refresh token.');
  129. }
  130. return new PostRefreshTokenAuthenticationToken(
  131. $passport->getUser(),
  132. $firewallName,
  133. $passport->getUser()->getRoles(),
  134. $refreshToken
  135. );
  136. }
  137. public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
  138. {
  139. return $this->successHandler->onAuthenticationSuccess($request, $token);
  140. }
  141. public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
  142. {
  143. return $this->failureHandler->onAuthenticationFailure($request, $exception);
  144. }
  145. public function start(Request $request, ?AuthenticationException $authException = null): Response
  146. {
  147. $event = new RefreshTokenNotFoundEvent(
  148. new MissingTokenException('JWT Refresh Token not found', 0, $authException),
  149. new RefreshAuthenticationFailureResponse($authException ? $authException->getMessageKey() : 'Authentication error')
  150. );
  151. $this->eventDispatcher->dispatch($event, 'gesdinet.refresh_token_not_found');
  152. return $event->getResponse();
  153. }
  154. }