src/Security/OAuth2BearerAuthenticator.php line 29

Open in your IDE?
  1. <?php
  2. namespace App\Security;
  3. use League\OAuth2\Server\Exception\OAuthServerException;
  4. use League\OAuth2\Server\ResourceServer;
  5. use Lexik\Bundle\JWTAuthenticationBundle\Security\Guard\JWTTokenAuthenticator;
  6. use Psr\Log\LoggerInterface;
  7. use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
  8. use Symfony\Component\HttpFoundation\Request;
  9. use Symfony\Component\HttpFoundation\Response;
  10. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  11. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  12. use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
  13. use Symfony\Component\Security\Core\User\UserInterface;
  14. use Symfony\Component\Security\Core\User\UserProviderInterface;
  15. use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
  16. use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
  17. use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
  18. use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
  19. /**
  20.  * Single API authenticator for "Authorization: Bearer" tokens. Tries league/oauth2-server
  21.  * access tokens first, then falls back to lexik JWT tokens (kept for backward compatibility
  22.  * with existing app builds). Both token types are JWTs on the same header, so handling them in
  23.  * one authenticator avoids the multi-authenticator firewall behaviour where a later failing
  24.  * authenticator overrides an earlier success.
  25.  */
  26. class OAuth2BearerAuthenticator extends AbstractAuthenticator
  27. {
  28.     public function __construct(
  29.         private HttpMessageFactoryInterface $httpMessageFactory,
  30.         private ResourceServer $resourceServer,
  31.         private UserProviderInterface $userProvider,
  32.         private JWTTokenAuthenticator $lexikAuthenticator,
  33.         private ?LoggerInterface $logger null
  34.     ) {
  35.     }
  36.     public function supports(Request $request): ?bool
  37.     {
  38.         return str_starts_with($request->headers->get('Authorization'''), 'Bearer ');
  39.     }
  40.     public function authenticate(Request $request): Passport
  41.     {
  42.         try {
  43.             $psrRequest $this->resourceServer->validateAuthenticatedRequest($this->httpMessageFactory->createRequest($request));
  44.             $userIdentifier = (string) $psrRequest->getAttribute('oauth_user_id''');
  45.             return new SelfValidatingPassport(new UserBadge($userIdentifier, function (string $id): UserInterface {
  46.                 return $this->userProvider->loadUserByIdentifier($id);
  47.             }));
  48.         } catch (OAuthServerException $e) {
  49.             $this->logger?->debug('Not an OAuth2 access token, falling back to lexik JWT.', ['message' => $e->getMessage()]);
  50.         }
  51.         // Fall back to lexik JWT: delegates token extraction, decoding and user loading.
  52.         $preAuthToken $this->lexikAuthenticator->getCredentials($request);
  53.         if (null === $preAuthToken) {
  54.             throw new CustomUserMessageAuthenticationException('No token provided.');
  55.         }
  56.         $user $this->lexikAuthenticator->getUser($preAuthToken$this->userProvider);
  57.         return new SelfValidatingPassport(new UserBadge($user->getUsername(), fn (): UserInterface => $user));
  58.     }
  59.     public function onAuthenticationSuccess(Request $requestTokenInterface $tokenstring $firewallName): ?Response
  60.     {
  61.         return null;
  62.     }
  63.     public function onAuthenticationFailure(Request $requestAuthenticationException $exception): ?Response
  64.     {
  65.         // Let the firewall entry point produce the 401.
  66.         return null;
  67.     }
  68. }