diff --git a/src/Security/EntryPoint/KeycloakAuthenticationEntryPoint.php b/src/Security/EntryPoint/KeycloakAuthenticationEntryPoint.php index b2f3380..c6695a4 100644 --- a/src/Security/EntryPoint/KeycloakAuthenticationEntryPoint.php +++ b/src/Security/EntryPoint/KeycloakAuthenticationEntryPoint.php @@ -5,6 +5,8 @@ namespace Mainick\KeycloakClientBundle\Security\EntryPoint; use Mainick\KeycloakClientBundle\DTO\KeycloakAuthorizationCodeEnum; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -15,16 +17,40 @@ final readonly class KeycloakAuthenticationEntryPoint implements AuthenticationEntryPointInterface { public function __construct( - private UrlGeneratorInterface $urlGenerator + private UrlGeneratorInterface $urlGenerator, + private ?LoggerInterface $keycloakClientLogger = null, ) { } public function start(Request $request, ?AuthenticationException $authException = null): Response { + // Handling AJAX requests + if ($request->isXmlHttpRequest()) { + return new JsonResponse( + [ + 'code' => Response::HTTP_UNAUTHORIZED, + 'message' => 'Authentication Required', + 'login_url' => $this->urlGenerator->generate('mainick_keycloak_security_auth_connect'), + ], + Response::HTTP_UNAUTHORIZED + ); + } + if ($request->hasSession()) { $request->getSession()->set(KeycloakAuthorizationCodeEnum::LOGIN_REFERRER, $request->getUri()); + + $request->getSession()->getBag('flashes')->add( + 'info', + 'Please log in to access this page', + ); } + $this->keycloakClientLogger?->info('KeycloakAuthenticationEntryPoint::start', [ + 'path' => $request->getPathInfo(), + 'error' => $authException?->getMessage(), + 'loginReferrer' => $request->getUri(), + ]); + return new RedirectResponse( $this->urlGenerator->generate('mainick_keycloak_security_auth_connect'), Response::HTTP_TEMPORARY_REDIRECT diff --git a/src/Security/User/KeycloakUserProvider.php b/src/Security/User/KeycloakUserProvider.php index 97f4b0d..fccaac9 100644 --- a/src/Security/User/KeycloakUserProvider.php +++ b/src/Security/User/KeycloakUserProvider.php @@ -8,6 +8,7 @@ use Mainick\KeycloakClientBundle\Interface\IamClientInterface; use Mainick\KeycloakClientBundle\Token\KeycloakResourceOwner; use Psr\Log\LoggerInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\Exception\UnsupportedUserException; use Symfony\Component\Security\Core\Exception\UserNotFoundException; use Symfony\Component\Security\Core\User\UserInterface; @@ -28,11 +29,33 @@ public function refreshUser(UserInterface $user): UserInterface } $accessToken = $user->getAccessToken(); - if ($accessToken && $accessToken->hasExpired()) { - $accessToken = $this->iamClient->refreshToken($accessToken); + if (!$accessToken) { + $this->keycloakClientLogger->error('KeycloakUserProvider::refreshUser', [ + 'message' => 'User does not have an access token.', + 'user_id' => $user->getUserIdentifier(), + ]); + throw new AuthenticationException('No valid access token available. Please login again.'); + } + + try { + if ($accessToken->hasExpired()) { + $accessToken = $this->iamClient->refreshToken($accessToken); + if (!$accessToken) { + throw new AuthenticationException('Failed to refresh user session. Please login again.'); + } + } + + return $this->loadUserByIdentifier($accessToken); } + catch (\Exception $e) { + $this->keycloakClientLogger->error('KeycloakUserProvider::refreshUser', [ + 'error' => $e->getMessage(), + 'message' => 'Failed to refresh user access token', + 'user_id' => $user->getUserIdentifier(), + ]); - return $this->loadUserByIdentifier($accessToken); + throw new AuthenticationException('Failed to refresh user session. Please login again.'); + } } public function supportsClass(string $class): bool @@ -49,21 +72,27 @@ public function loadUserByIdentifier($identifier): UserInterface try { $resourceOwner = $this->iamClient->fetchUserFromToken($identifier); if (!$resourceOwner) { - throw new UserNotFoundException(sprintf('User with access token "%s" not found.', $identifier)); + $this->keycloakClientLogger->info('KeycloakUserProvider::loadUserByIdentifier', [ + 'message' => 'User not found', + 'token' => $identifier->getToken(), + ]); + throw new UserNotFoundException('User not found or invalid token.'); } + $this->keycloakClientLogger->info('KeycloakUserProvider::loadUserByIdentifier', [ 'resourceOwner' => $resourceOwner->toArray(), ]); + + return $resourceOwner; } catch (\UnexpectedValueException $e) { $this->keycloakClientLogger->warning('KeycloakUserProvider::loadUserByIdentifier', [ 'error' => $e->getMessage(), 'message' => 'User should have been disconnected from Keycloak server', + 'token' => $identifier->getToken(), ]); - throw new UserNotFoundException(sprintf('User with access token "%s" not found.', $identifier)); + throw new UserNotFoundException('Failed to load user from token.'); } - - return $resourceOwner; } }