src/EventSubscriber/OAuth2TokenResponseSubscriber.php line 31

Open in your IDE?
  1. <?php
  2. namespace App\EventSubscriber;
  3. use App\Entity\User;
  4. use App\Service\IdTokenGenerator;
  5. use Doctrine\ORM\EntityManagerInterface;
  6. use Lcobucci\JWT\Encoding\JoseEncoder;
  7. use Lcobucci\JWT\Token\Parser as TokenParser;
  8. use Psr\Log\LoggerInterface;
  9. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  10. use Symfony\Component\HttpFoundation\JsonResponse;
  11. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  12. use Symfony\Component\HttpKernel\KernelEvents;
  13. class OAuth2TokenResponseSubscriber implements EventSubscriberInterface
  14. {
  15.     public function __construct(
  16.         private IdTokenGenerator $idTokenGenerator,
  17.         private LoggerInterface $logger,
  18.         private EntityManagerInterface $entityManager,
  19.     ) {}
  20.     public static function getSubscribedEvents(): array
  21.     {
  22.         return [
  23.             KernelEvents::RESPONSE => 'onResponse',
  24.         ];
  25.     }
  26.     public function onResponse(ResponseEvent $event): void
  27.     {
  28.         $request $event->getRequest();
  29.         $response $event->getResponse();
  30.         
  31.         // Only intercept /token endpoint
  32.         if ($request->getPathInfo() !== '/token') {
  33.             return;
  34.         }
  35.         
  36.         // Only intercept successful responses
  37.         if ($response->getStatusCode() !== 200) {
  38.             return;
  39.         }
  40.         
  41.         $this->logger->info('[OAuth2TokenResponseSubscriber] Token endpoint intercepted');
  42.         
  43.         try {
  44.             $content $response->getContent();
  45.             $data json_decode($contenttrue);
  46.             
  47.             if (!is_array($data) || !isset($data['access_token'])) {
  48.                 $this->logger->warning('[OAuth2TokenResponseSubscriber] No access_token in response');
  49.                 return;
  50.             }
  51.             $accessToken $data['access_token'];
  52.             
  53.             // Parse JWT to extract claims and scopes
  54.             $parser = new TokenParser(new JoseEncoder());
  55.             $token $parser->parse($accessToken);
  56.             
  57.             if (!$token instanceof \Lcobucci\JWT\UnencryptedToken) {
  58.                 $this->logger->warning('[OAuth2TokenResponseSubscriber] Token is not unencrypted');
  59.                 return;
  60.             }
  61.             $claims = [];
  62.             $scopes = [];
  63.             $clientId null;
  64.             $allClaims $token->claims()->all();
  65.             
  66.             foreach ($allClaims as $key => $value) {
  67.                 if ($key === 'scopes') {
  68.                     $scopes is_array($value) ? $value : (array) $value;
  69.                 } elseif ($key === 'aud') {
  70.                     // audience = client_id
  71.                     $clientId is_array($value) ? $value[0] : $value;
  72.                     $claims[$key] = $value;
  73.                 } else {
  74.                     $claims[$key] = $value;
  75.                 }
  76.             }
  77.             
  78.             $this->logger->info('[OAuth2TokenResponseSubscriber] Scopes:', ['scopes' => $scopes]);
  79.             $this->logger->info('[OAuth2TokenResponseSubscriber] Client ID:', ['client_id' => $clientId]);
  80.             $this->logger->info('[OAuth2TokenResponseSubscriber] Original sub (email):', ['sub' => $claims['sub'] ?? 'none']);
  81.             
  82.             // Get user from database using email (sub contains email in access_token)
  83.             $userEmail $claims['sub'] ?? null;
  84.             if ($userEmail) {
  85.                 $user $this->entityManager->getRepository(User::class)->findOneBy(['email' => $userEmail]);
  86.                 if ($user) {
  87.                     // Replace sub with user ID for id_token generation
  88.                     $claims['sub'] = (string) $user->getId();
  89.                     $this->logger->info('[OAuth2TokenResponseSubscriber] Replaced sub with user ID:', ['sub' => $claims['sub']]);
  90.                 } else {
  91.                     $this->logger->warning('[OAuth2TokenResponseSubscriber] User not found for email:', ['email' => $userEmail]);
  92.                 }
  93.             }
  94.             
  95.             // Only generate client_id token if openid scope is requested
  96.             if (!in_array('openid'$scopes)) {
  97.                 $this->logger->info('[OAuth2TokenResponseSubscriber] openid scope not requested, skipping client_id generation');
  98.                 
  99.                 // Return response without modification
  100.                 $newResponse = new JsonResponse($data);
  101.                 $newResponse->headers->set('Content-Type''application/json');
  102.                 $event->setResponse($newResponse);
  103.                 return;
  104.             }
  105.             $this->logger->info('[OAuth2TokenResponseSubscriber] Generating id_token...', ['claims' => $claims]);
  106.             // Generate id_token with user claims
  107.             $idToken $this->idTokenGenerator->generateIdToken($claims$scopes);
  108.             if ($idToken) {
  109.                 $this->logger->info('[OAuth2TokenResponseSubscriber] id_token generated successfully');
  110.                 $data['id_token'] = $idToken;
  111.                 
  112.                 
  113.             } else {
  114.                 $this->logger->warning('[OAuth2TokenResponseSubscriber] Failed to generate id_token');
  115.             }
  116.             
  117.             // Return updated response
  118.             $newResponse = new JsonResponse($data);
  119.             $newResponse->headers->set('Content-Type''application/json');
  120.             $event->setResponse($newResponse);
  121.             
  122.         } catch (\Exception $e) {
  123.             $this->logger->error('[OAuth2TokenResponseSubscriber] Error: ' $e->getMessage(), [
  124.                 'exception' => $e,
  125.                 'trace' => $e->getTraceAsString()
  126.             ]);
  127.         }
  128.     }
  129. }