vendor/lexik/jwt-authentication-bundle/Security/Guard/JWTTokenAuthenticator.php line 201

Open in your IDE?
  1. <?php
  2. namespace Lexik\Bundle\JWTAuthenticationBundle\Security\Guard;
  3. use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTAuthenticatedEvent;
  4. use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTExpiredEvent;
  5. use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTInvalidEvent;
  6. use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTNotFoundEvent;
  7. use Lexik\Bundle\JWTAuthenticationBundle\Events;
  8. use Lexik\Bundle\JWTAuthenticationBundle\Exception\ExpiredTokenException;
  9. use Lexik\Bundle\JWTAuthenticationBundle\Exception\InvalidPayloadException;
  10. use Lexik\Bundle\JWTAuthenticationBundle\Exception\InvalidTokenException;
  11. use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTDecodeFailureException;
  12. use Lexik\Bundle\JWTAuthenticationBundle\Exception\MissingTokenException;
  13. use Lexik\Bundle\JWTAuthenticationBundle\Exception\UserNotFoundException;
  14. use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;
  15. use Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\JWTUserToken;
  16. use Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\PreAuthenticationJWTUserToken;
  17. use Lexik\Bundle\JWTAuthenticationBundle\Security\Authentication\Token\PreAuthenticationJWTUserTokenInterface;
  18. use Lexik\Bundle\JWTAuthenticationBundle\Security\User\PayloadAwareUserProviderInterface;
  19. use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
  20. use Lexik\Bundle\JWTAuthenticationBundle\TokenExtractor\TokenExtractorInterface;
  21. use Symfony\Component\HttpFoundation\Request;
  22. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  23. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  24. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  25. use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
  26. use Symfony\Component\Security\Core\Exception\UserNotFoundException as SecurityUserNotFoundException;
  27. use Symfony\Component\Security\Core\User\ChainUserProvider;
  28. use Symfony\Component\Security\Core\User\UserInterface;
  29. use Symfony\Component\Security\Core\User\UserProviderInterface;
  30. use Symfony\Component\Security\Guard\AuthenticatorInterface;
  31. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  32. use Symfony\Contracts\Translation\TranslatorInterface;
  33. /**
  34. * JWTTokenAuthenticator (Guard implementation).
  35. *
  36. * @see http://knpuniversity.com/screencast/symfony-rest4/jwt-guard-authenticator
  37. *
  38. * @author Nicolas Cabot <n.cabot@lexik.fr>
  39. * @author Robin Chalas <robin.chalas@gmail.com>
  40. */
  41. class JWTTokenAuthenticator implements AuthenticatorInterface
  42. {
  43. /**
  44. * @var JWTTokenManagerInterface
  45. */
  46. private $jwtManager;
  47. /**
  48. * @var EventDispatcherInterface
  49. */
  50. private $dispatcher;
  51. /**
  52. * @var TokenExtractorInterface
  53. */
  54. private $tokenExtractor;
  55. /**
  56. * @var TokenStorageInterface
  57. */
  58. private $preAuthenticationTokenStorage;
  59. /**
  60. * @var TranslatorInterface
  61. */
  62. private $translator;
  63. public function __construct(
  64. JWTTokenManagerInterface $jwtManager,
  65. EventDispatcherInterface $dispatcher,
  66. TokenExtractorInterface $tokenExtractor,
  67. TokenStorageInterface $preAuthenticationTokenStorage,
  68. TranslatorInterface $translator = null
  69. ) {
  70. $this->jwtManager = $jwtManager;
  71. $this->dispatcher = $dispatcher;
  72. $this->tokenExtractor = $tokenExtractor;
  73. $this->preAuthenticationTokenStorage = $preAuthenticationTokenStorage;
  74. $this->translator = $translator;
  75. }
  76. public function supports(Request $request)
  77. {
  78. return false !== $this->getTokenExtractor()->extract($request);
  79. }
  80. /**
  81. * Returns a decoded JWT token extracted from a request.
  82. *
  83. * {@inheritdoc}
  84. *
  85. * @return PreAuthenticationJWTUserTokenInterface
  86. *
  87. * @throws InvalidTokenException If an error occur while decoding the token
  88. * @throws ExpiredTokenException If the request token is expired
  89. */
  90. public function getCredentials(Request $request)
  91. {
  92. $tokenExtractor = $this->getTokenExtractor();
  93. if (!$tokenExtractor instanceof TokenExtractorInterface) {
  94. throw new \RuntimeException(sprintf('Method "%s::getTokenExtractor()" must return an instance of "%s".', self::class, TokenExtractorInterface::class));
  95. }
  96. if (false === ($jsonWebToken = $tokenExtractor->extract($request))) {
  97. return;
  98. }
  99. $preAuthToken = new PreAuthenticationJWTUserToken($jsonWebToken);
  100. try {
  101. if (!$payload = $this->jwtManager->decode($preAuthToken)) {
  102. throw new InvalidTokenException('Invalid JWT Token');
  103. }
  104. $preAuthToken->setPayload($payload);
  105. } catch (JWTDecodeFailureException $e) {
  106. if (JWTDecodeFailureException::EXPIRED_TOKEN === $e->getReason()) {
  107. $expiredTokenException = new ExpiredTokenException();
  108. $expiredTokenException->setToken($preAuthToken);
  109. throw $expiredTokenException;
  110. }
  111. throw new InvalidTokenException('Invalid JWT Token', 0, $e);
  112. }
  113. return $preAuthToken;
  114. }
  115. /**
  116. * Returns an user object loaded from a JWT token.
  117. *
  118. * {@inheritdoc}
  119. *
  120. * @param PreAuthenticationJWTUserTokenInterface $preAuthToken Implementation of the (Security) TokenInterface
  121. *
  122. * @throws \InvalidArgumentException If preAuthToken is not of the good type
  123. * @throws InvalidPayloadException If the user identity field is not a key of the payload
  124. * @throws UserNotFoundException If no user can be loaded from the given token
  125. */
  126. public function getUser($preAuthToken, UserProviderInterface $userProvider)
  127. {
  128. if (!$preAuthToken instanceof PreAuthenticationJWTUserTokenInterface) {
  129. throw new \InvalidArgumentException(sprintf('The first argument of the "%s()" method must be an instance of "%s".', __METHOD__, PreAuthenticationJWTUserTokenInterface::class));
  130. }
  131. $payload = $preAuthToken->getPayload();
  132. $idClaim = $this->jwtManager->getUserIdClaim();
  133. if (!isset($payload[$idClaim])) {
  134. throw new InvalidPayloadException($idClaim);
  135. }
  136. $user = $this->loadUser($userProvider, $payload, $payload[$idClaim]);
  137. $this->preAuthenticationTokenStorage->setToken($preAuthToken);
  138. return $user;
  139. }
  140. /**
  141. * {@inheritdoc}
  142. */
  143. public function onAuthenticationFailure(Request $request, AuthenticationException $authException)
  144. {
  145. $errorMessage = strtr($authException->getMessageKey(), $authException->getMessageData());
  146. if (null !== $this->translator) {
  147. $errorMessage = $this->translator->trans($authException->getMessageKey(), $authException->getMessageData(), 'security');
  148. }
  149. $response = new JWTAuthenticationFailureResponse($errorMessage);
  150. if ($authException instanceof ExpiredTokenException) {
  151. $event = new JWTExpiredEvent($authException, $response, $request);
  152. $eventName = Events::JWT_EXPIRED;
  153. } else {
  154. $event = new JWTInvalidEvent($authException, $response, $request);
  155. $eventName = Events::JWT_INVALID;
  156. }
  157. $this->dispatcher->dispatch($event, $eventName);
  158. return $event->getResponse();
  159. }
  160. /**
  161. * {@inheritdoc}
  162. */
  163. public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
  164. {
  165. return;
  166. }
  167. /**
  168. * {@inheritdoc}
  169. *
  170. * @return JWTAuthenticationFailureResponse
  171. */
  172. public function start(Request $request, AuthenticationException $authException = null)
  173. {
  174. $exception = new MissingTokenException('JWT Token not found', 0, $authException);
  175. $event = new JWTNotFoundEvent($exception, new JWTAuthenticationFailureResponse($exception->getMessageKey()), $request);
  176. $this->dispatcher->dispatch($event, Events::JWT_NOT_FOUND);
  177. return $event->getResponse();
  178. }
  179. /**
  180. * {@inheritdoc}
  181. */
  182. public function checkCredentials($credentials, UserInterface $user)
  183. {
  184. return true;
  185. }
  186. /**
  187. * {@inheritdoc}
  188. *
  189. * @throws \RuntimeException If there is no pre-authenticated token previously stored
  190. */
  191. public function createAuthenticatedToken(UserInterface $user, $providerKey)
  192. {
  193. $preAuthToken = $this->preAuthenticationTokenStorage->getToken();
  194. if (null === $preAuthToken) {
  195. throw new \RuntimeException('Unable to return an authenticated token since there is no pre authentication token.');
  196. }
  197. $authToken = new JWTUserToken($user->getRoles(), $user, $preAuthToken->getCredentials(), $providerKey);
  198. $this->dispatcher->dispatch(new JWTAuthenticatedEvent($preAuthToken->getPayload(), $authToken), Events::JWT_AUTHENTICATED);
  199. $this->preAuthenticationTokenStorage->setToken(null);
  200. return $authToken;
  201. }
  202. /**
  203. * {@inheritdoc}
  204. */
  205. public function supportsRememberMe()
  206. {
  207. return false;
  208. }
  209. /**
  210. * Gets the token extractor to be used for retrieving a JWT token in the
  211. * current request.
  212. *
  213. * Override this method for adding/removing extractors to the chain one or
  214. * returning a different {@link TokenExtractorInterface} implementation.
  215. *
  216. * @return TokenExtractorInterface
  217. */
  218. protected function getTokenExtractor()
  219. {
  220. return $this->tokenExtractor;
  221. }
  222. /**
  223. * @return JWTTokenManagerInterface
  224. */
  225. protected function getJwtManager()
  226. {
  227. return $this->jwtManager;
  228. }
  229. /**
  230. * @return EventDispatcherInterface
  231. */
  232. protected function getDispatcher()
  233. {
  234. return $this->dispatcher;
  235. }
  236. /**
  237. * @return TokenStorageInterface
  238. */
  239. protected function getPreAuthenticationTokenStorage()
  240. {
  241. return $this->preAuthenticationTokenStorage;
  242. }
  243. /**
  244. * Loads the user to authenticate.
  245. *
  246. * @param UserProviderInterface $userProvider An user provider
  247. * @param array $payload The token payload
  248. * @param string $identity The key from which to retrieve the user "username"
  249. *
  250. * @return UserInterface
  251. */
  252. protected function loadUser(UserProviderInterface $userProvider, array $payload, $identity)
  253. {
  254. $providers = $userProvider instanceof ChainUserProvider ? $userProvider->getProviders() : [$userProvider];
  255. foreach ($providers as $provider) {
  256. try {
  257. if ($provider instanceof PayloadAwareUserProviderInterface) {
  258. return $provider->loadUserByUsernameAndPayload($identity, $payload);
  259. }
  260. if (method_exists($provider, 'loadUserByIdentifier')) {
  261. return $provider->loadUserByIdentifier($identity);
  262. }
  263. return $provider->loadUserByUsername($identity);
  264. } catch (SecurityUserNotFoundException | UsernameNotFoundException $e) {
  265. // try next one
  266. }
  267. }
  268. if (class_exists(SecurityUserNotFoundException::class)) {
  269. $ex = new SecurityUserNotFoundException(sprintf('There is no user with name "%s".', $identity));
  270. $ex->setUserIdentifier($identity);
  271. } else {
  272. $ex = new UsernameNotFoundException(sprintf('There is no user with name "%s".', $identity));
  273. $ex->setUsername($identity);
  274. }
  275. throw $ex;
  276. }
  277. }