src/Infrastructure/Auth/Symfony/Security/Authenticator/SiteAuthenticator.php line 20

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Infrastructure\Auth\Symfony\Security\Authenticator;
  4. use Symfony\Component\HttpFoundation\JsonResponse;
  5. use Symfony\Component\HttpFoundation\Request;
  6. use Symfony\Component\HttpFoundation\Response;
  7. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  8. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  9. use Symfony\Component\Security\Core\Exception\BadCredentialsException;
  10. use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
  11. use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
  12. use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
  13. use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
  14. use Psr\Log\LoggerAwareInterface;
  15. use Psr\Log\LoggerAwareTrait;
  16. class SiteAuthenticator extends AbstractAuthenticator implements LoggerAwareInterface
  17. {
  18.     use LoggerAwareTrait;
  19.     /**
  20.      * Paths under the /api firewall that are PUBLIC_ACCESS (see security.yaml)
  21.      * and must never be routed through site Basic-auth, regardless of what
  22.      * Authorization header (if any) the caller happens to send.
  23.      */
  24.     private const PUBLIC_PATH_PATTERNS = [
  25.         '#^/api/docs#',
  26.         '#^/api/direct-debit#',
  27.         '#^/api/recurring-payments#',
  28.         '#^/api/finxp-callback#',
  29.     ];
  30.     public function supports(Request $request): ?bool
  31.     {
  32.         foreach (self::PUBLIC_PATH_PATTERNS as $pattern) {
  33.             if (preg_match($pattern, $request->getPathInfo()) === 1) {
  34.                 return false;
  35.             }
  36.         }
  37.         return $request->headers->has('Authorization');
  38.     }
  39.     public function authenticate(Request $request): Passport
  40.     {
  41.         $header = (string) $request->headers->get('Authorization', '');
  42.         $username = null;
  43.         $password = null;
  44.         if (str_starts_with(strtolower($header), 'basic')) {
  45.             $decoded = base64_decode(trim(substr($header, 6)), true);
  46.             $basic = $decoded !== false ? explode(':', $decoded, 2) : [];
  47.             $username = $basic[0] ?? null;
  48.             $password = $basic[1] ?? null;
  49.         }
  50.         if ($username === null || $password === null) {
  51.             throw new BadCredentialsException('Missing or malformed Authorization header.');
  52.         }
  53.         return new Passport(new UserBadge($username), new PasswordCredentials($password));
  54.     }
  55.     public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
  56.     {
  57.         return null;
  58.     }
  59.     public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
  60.     {
  61.         $this->logger->error('Merchant authentication failed: ' . $exception->getMessage());
  62.         return new JsonResponse([
  63.             'error' => 'Authorization required',
  64.             'error_code' => 401,
  65.         ], Response::HTTP_UNAUTHORIZED);
  66.     }
  67. }