vendor/lexik/jwt-authentication-bundle/Security/Authenticator/JWTAuthenticator.php line 211

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Lexik\Bundle\JWTAuthenticationBundle\Security\Authenticator;
  4. use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTAuthenticatedEvent;
  5. use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTExpiredEvent;
  6. use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTInvalidEvent;
  7. use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTNotFoundEvent;
  8. use Lexik\Bundle\JWTAuthenticationBundle\Events;
  9. use Lexik\Bundle\JWTAuthenticationBundle\Exception\ExpiredTokenException;
  10. use Lexik\Bundle\JWTAuthenticationBundle\Exception\InvalidPayloadException;
  11. use Lexik\Bundle\JWTAuthenticationBundle\Exception\InvalidTokenException;
  12. use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;
  13. use Lexik\Bundle\JWTAuthenticationBundle\Exception\MissingTokenException;
  14. use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;
  15. use Lexik\Bundle\JWTAuthenticationBundle\Security\Authenticator\Token\JWTPostAuthenticationToken;
  16. use Lexik\Bundle\JWTAuthenticationBundle\Security\User\PayloadAwareUserProviderInterface;
  17. use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
  18. use Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor\TokenExtractorInterface;
  19. use Symfony\Component\HttpFoundation\Request;
  20. use Symfony\Component\HttpFoundation\Response;
  21. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  22. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  23. use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
  24. use Symfony\Component\Security\Core\Exception\UserNotFoundException;
  25. use Symfony\Component\Security\Core\User\ChainUserProvider;
  26. use Symfony\Component\Security\Core\User\UserInterface;
  27. use Symfony\Component\Security\Core\User\UserProviderInterface;
  28. use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
  29. use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
  30. use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
  31. use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
  32. use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
  33. use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
  34. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  35. use Symfony\Contracts\Translation\TranslatorInterface;
  36. class JWTAuthenticator extends AbstractAuthenticator implements AuthenticationEntryPointInterface
  37. {
  38. use ForwardCompatAuthenticatorTrait;
  39. /**
  40. * @var TokenExtractorInterface
  41. */
  42. private $tokenExtractor;
  43. /**
  44. * @var JWTTokenManagerInterface
  45. */
  46. private $jwtManager;
  47. /**
  48. * @var EventDispatcherInterface
  49. */
  50. private $eventDispatcher;
  51. /**
  52. * @var UserProviderInterface
  53. */
  54. private $userProvider;
  55. /**
  56. * @var TranslatorInterface|null
  57. */
  58. private $translator;
  59. public function __construct(
  60. JWTTokenManagerInterface $jwtManager,
  61. EventDispatcherInterface $eventDispatcher,
  62. TokenExtractorInterface $tokenExtractor,
  63. UserProviderInterface $userProvider,
  64. TranslatorInterface $translator = null
  65. ) {
  66. $this->tokenExtractor = $tokenExtractor;
  67. $this->jwtManager = $jwtManager;
  68. $this->eventDispatcher = $eventDispatcher;
  69. $this->userProvider = $userProvider;
  70. $this->translator = $translator;
  71. }
  72. /**
  73. * {@inheritdoc}
  74. */
  75. public function start(Request $request, AuthenticationException $authException = null): Response
  76. {
  77. $exception = new MissingTokenException('JWT Token not found', 0, $authException);
  78. $event = new JWTNotFoundEvent($exception, new JWTAuthenticationFailureResponse($exception->getMessageKey()), $request);
  79. $this->eventDispatcher->dispatch($event, Events::JWT_NOT_FOUND);
  80. return $event->getResponse();
  81. }
  82. public function supports(Request $request): ?bool
  83. {
  84. return false !== $this->getTokenExtractor()->extract($request);
  85. }
  86. /**
  87. * @return Passport
  88. */
  89. public function doAuthenticate(Request $request) /*: Passport */
  90. {
  91. $token = $this->getTokenExtractor()->extract($request);
  92. if ($token === false) {
  93. throw new \LogicException('Unable to extract a JWT token from the request. Also, make sure to call `supports()` before `authenticate()` to get a proper client error.');
  94. }
  95. try {
  96. if (!$payload = $this->jwtManager->parse($token)) {
  97. throw new InvalidTokenException('Invalid JWT Token');
  98. }
  99. } catch (JWTDecodeFailureException $e) {
  100. if (JWTDecodeFailureException::EXPIRED_TOKEN === $e->getReason()) {
  101. throw new ExpiredTokenException();
  102. }
  103. throw new InvalidTokenException('Invalid JWT Token', 0, $e);
  104. }
  105. $idClaim = $this->jwtManager->getUserIdClaim();
  106. if (!isset($payload[$idClaim])) {
  107. throw new InvalidPayloadException($idClaim);
  108. }
  109. $passport = new SelfValidatingPassport(
  110. new UserBadge(
  111. (string)$payload[$idClaim],
  112. function ($userIdentifier) use ($payload) {
  113. return $this->loadUser($payload, $userIdentifier);
  114. }
  115. )
  116. );
  117. $passport->setAttribute('payload', $payload);
  118. $passport->setAttribute('token', $token);
  119. return $passport;
  120. }
  121. public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
  122. {
  123. return null;
  124. }
  125. public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
  126. {
  127. $errorMessage = strtr($exception->getMessageKey(), $exception->getMessageData());
  128. if (null !== $this->translator) {
  129. $errorMessage = $this->translator->trans($exception->getMessageKey(), $exception->getMessageData(), 'security');
  130. }
  131. $response = new JWTAuthenticationFailureResponse($errorMessage);
  132. if ($exception instanceof ExpiredTokenException) {
  133. $event = new JWTExpiredEvent($exception, $response, $request);
  134. $eventName = Events::JWT_EXPIRED;
  135. } else {
  136. $event = new JWTInvalidEvent($exception, $response, $request);
  137. $eventName = Events::JWT_INVALID;
  138. }
  139. $this->eventDispatcher->dispatch($event, $eventName);
  140. return $event->getResponse();
  141. }
  142. /**
  143. * Gets the token extractor to be used for retrieving a JWT token in the
  144. * current request.
  145. *
  146. * Override this method for adding/removing extractors to the chain one or
  147. * returning a different {@link TokenExtractorInterface} implementation.
  148. */
  149. protected function getTokenExtractor(): TokenExtractorInterface
  150. {
  151. return $this->tokenExtractor;
  152. }
  153. /**
  154. * Gets the jwt manager.
  155. */
  156. protected function getJwtManager(): JWTTokenManagerInterface
  157. {
  158. return $this->jwtManager;
  159. }
  160. /**
  161. * Gets the event dispatcher.
  162. */
  163. protected function getEventDispatcher(): EventDispatcherInterface
  164. {
  165. return $this->eventDispatcher;
  166. }
  167. /**
  168. * Gets the user provider.
  169. */
  170. protected function getUserProvider(): UserProviderInterface
  171. {
  172. return $this->userProvider;
  173. }
  174. /**
  175. * Loads the user to authenticate.
  176. *
  177. * @param array $payload The token payload
  178. * @param string $identity The key from which to retrieve the user "identifier"
  179. */
  180. protected function loadUser(array $payload, string $identity): UserInterface
  181. {
  182. if ($this->userProvider instanceof PayloadAwareUserProviderInterface) {
  183. if (method_exists($this->userProvider, 'loadUserByIdentifierAndPayload')) {
  184. return $this->userProvider->loadUserByIdentifierAndPayload($identity, $payload);
  185. } else {
  186. return $this->userProvider->loadUserByUsernameAndPayload($identity, $payload);
  187. }
  188. }
  189. if ($this->userProvider instanceof ChainUserProvider) {
  190. foreach ($this->userProvider->getProviders() as $provider) {
  191. try {
  192. if ($provider instanceof PayloadAwareUserProviderInterface) {
  193. if (method_exists(PayloadAwareUserProviderInterface::class, 'loadUserByIdentifierAndPayload')) {
  194. return $provider->loadUserByIdentifierAndPayload($identity, $payload);
  195. } else {
  196. return $provider->loadUserByUsernameAndPayload($identity, $payload);
  197. }
  198. }
  199. return $provider->loadUserByIdentifier($identity);
  200. // More generic call to catch both UsernameNotFoundException for SF<5.3 and new UserNotFoundException
  201. } catch (AuthenticationException $e) {
  202. // try next one
  203. }
  204. }
  205. if (!class_exists(UserNotFoundException::class)) {
  206. $ex = new UsernameNotFoundException(sprintf('There is no user with username "%s".', $identity));
  207. $ex->setUsername($identity);
  208. } else {
  209. $ex = new UserNotFoundException(sprintf('There is no user with identifier "%s".', $identity));
  210. $ex->setUserIdentifier($identity);
  211. }
  212. throw $ex;
  213. }
  214. if (method_exists($this->userProvider, 'loadUserByIdentifier')) {
  215. return $this->userProvider->loadUserByIdentifier($identity);
  216. } else {
  217. return $this->userProvider->loadUserByUsername($identity);
  218. }
  219. }
  220. public function createAuthenticatedToken(PassportInterface $passport, string $firewallName): TokenInterface
  221. {
  222. if (!$passport instanceof Passport) {
  223. throw new \LogicException(sprintf('Expected "%s" but got "%s".', Passport::class, get_debug_type($passport)));
  224. }
  225. $token = new JWTPostAuthenticationToken($passport->getUser(), $firewallName, $passport->getUser()->getRoles(), $passport->getAttribute('token'));
  226. $this->eventDispatcher->dispatch(new JWTAuthenticatedEvent($passport->getAttribute('payload'), $token), Events::JWT_AUTHENTICATED);
  227. return $token;
  228. }
  229. public function createToken(Passport $passport, string $firewallName): TokenInterface
  230. {
  231. $token = new JWTPostAuthenticationToken($passport->getUser(), $firewallName, $passport->getUser()->getRoles(), $passport->getAttribute('token'));
  232. $this->eventDispatcher->dispatch(new JWTAuthenticatedEvent($passport->getAttribute('payload'), $token), Events::JWT_AUTHENTICATED);
  233. return $token;
  234. }
  235. }