Reputation: 1126
For my Symfony4 app, I would like :
Sounded good to have one dedicated Guard authenticator for each (especially for redirect after login, get login page...). I used make
to create guard files boilerplate, and I ended up to files below.
While login in middle-office works straightfully, I keep having a redirect loop when I try to go to /admin
part.
Logs show that admin
firewall is correctly catched as the good one here, but although I put app_admin_login
OK for anonymous access, it keeps redirecting to entry point which is admin/login
. Why so?
I surely miss somehting here, thanks for your enlightments.
[2019-02-13 11:46:39] request.INFO: Matched route "easyadmin". {"route":"easyadmin","route_parameters":{"_controller":"Symfony\\Bundle\\FrameworkBundle\\Controller\\RedirectController::urlRedirectAction","path":"/admin/","permanent":true,"scheme":null,"httpPort":8900,"httpsPort":443,"_route":"easyadmin"},"request_uri":"http://localhost:8900/admin","method":"GET"} []
[2019-02-13 11:46:39] security.DEBUG: Checking for guard authentication credentials. {"firewall_key":"admin","authenticators":1} []
[2019-02-13 11:46:39] security.DEBUG: Checking support on guard authenticator. {"firewall_key":"admin","authenticator":"App\\Security\\AdminLoginFormAuthenticator"} []
[2019-02-13 11:46:39] security.DEBUG: Guard authenticator does not support the request. {"firewall_key":"admin","authenticator":"App\\Security\\AdminLoginFormAuthenticator"} []
[2019-02-13 11:46:39] security.INFO: An AuthenticationException was thrown; redirecting to authentication entry point. {"exception":"[object] (Symfony\\Component\\Security\\Core\\Exception\\AuthenticationCredentialsNotFoundException(code: 0): A Token was not found in the TokenStorage. at /var/www/html/vendor/symfony/security-http/Firewall/AccessListener.php:51)"} []
[2019-02-13 11:46:39] security.DEBUG: Calling Authentication entry point. [] []
[2019-02-13 11:46:39] request.INFO: Matched route "app_admin_login". {"route":"app_admin_login","route_parameters":{"_route":"app_admin_login","_controller":"App\\Controller\\SecurityController::adminLogin"},"request_uri":"http://localhost:8900/admin/login","method":"GET"} []
[2019-02-13 11:46:39] security.DEBUG: Checking for guard authentication credentials. {"firewall_key":"admin","authenticators":1} []
[2019-02-13 11:46:39] security.DEBUG: Checking support on guard authenticator. {"firewall_key":"admin","authenticator":"App\\Security\\AdminLoginFormAuthenticator"} []
[2019-02-13 11:46:39] security.DEBUG: Guard authenticator does not support the request. {"firewall_key":"admin","authenticator":"App\\Security\\AdminLoginFormAuthenticator"} []
[2019-02-13 11:46:39] security.INFO: An AuthenticationException was thrown; redirecting to authentication entry point. {"exception":"[object] (Symfony\\Component\\Security\\Core\\Exception\\AuthenticationCredentialsNotFoundException(code: 0): A Token was not found in the TokenStorage. at /var/www/html/vendor/symfony/security-http/Firewall/AccessListener.php:51)"} []
[2019-02-13 11:46:39] security.DEBUG: Calling Authentication entry point. [] []
[2019-02-13 11:46:39] request.INFO: Matched route "app_admin_login". {"route":"app_admin_login","route_parameters":{"_route":"app_admin_login","_controller":"App\\Controller\\SecurityController::adminLogin"},"request_uri":"http://localhost:8900/admin/login","method":"GET"} []
[2019-02-13 11:46:39] security.DEBUG: Checking for guard authentication credentials. {"firewall_key":"admin","authenticators":1} []
[2019-02-13 11:46:39] security.DEBUG: Checking support on guard authenticator. {"firewall_key":"admin","authenticator":"App\\Security\\AdminLoginFormAuthenticator"} []
[2019-02-13 11:46:39] security.DEBUG: Guard authenticator does not support the request. {"firewall_key":"admin","authenticator":"App\\Security\\AdminLoginFormAuthenticator"} []
[2019-02-13 11:46:39] security.INFO: An AuthenticationException was thrown; redirecting to authentication entry point. {"exception":"[object] (Symfony\\Component\\Security\\Core\\Exception\\AuthenticationCredentialsNotFoundException(code: 0): A Token was not found in the TokenStorage. at /var/www/html/vendor/symfony/security-http/Firewall/AccessListener.php:51)"} []
[2019-02-13 11:46:39] security.DEBUG: Calling Authentication entry point. [] []
... Until max loop iteration number reached
security.yaml
security:
encoders:
App\Entity\Shop\User:
algorithm: argon2i
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\Shop\User
property: email
role_hierarchy:
ROLE_ADMIN: ROLE_USER
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
admin:
pattern: ^/admin
logout:
path: app_logout
guard:
authenticators:
- App\Security\AdminLoginFormAuthenticator
# logout_on_user_change: true
main:
pattern: ^/
anonymous: true
logout:
path: app_logout
guard:
authenticators:
- App\Security\LoginFormAuthenticator
# logout_on_user_change: true
# activate different ways to authenticate
# http_basic: true
# https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate
# form_login: true
# https://symfony.com/doc/current/security/form_login_setup.html
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
- { path: ^/admin/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/, roles: ROLE_USER }
AdminLoginFormAuthenticator
class AdminLoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
use TargetPathTrait;
private $entityManager;
private $urlGenerator;
private $csrfTokenManager;
private $passwordEncoder;
private $request;
public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
{
$this->entityManager = $entityManager;
$this->urlGenerator = $urlGenerator;
$this->csrfTokenManager = $csrfTokenManager;
$this->passwordEncoder = $passwordEncoder;
}
public function supports(Request $request)
{
$this->request = $request;
return ('app_admin_login' === $request->attributes->get('_route'))
&& $request->isMethod('POST');
}
public function getCredentials(Request $request)
{
$credentials = [
'email' => $request->request->get('email'),
'password' => $request->request->get('password'),
'csrf_token' => $request->request->get('_csrf_token'),
];
$request->getSession()->set(
Security::LAST_USERNAME,
$credentials['email']
);
return $credentials;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
if (!$this->csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $credentials['email']]);
if (!$user) {
// fail authentication with a custom error
throw new CustomUserMessageAuthenticationException('Email could not be found.');
}
return $user;
}
public function checkCredentials($credentials, UserInterface $user)
{
return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
$user = $token->getUser();
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
return new RedirectResponse($this->urlGenerator->generate('easyadmin'));
}
protected function getLoginUrl()
{
return $this->urlGenerator->generate('app_admin_login');
}
}
Upvotes: 2
Views: 4045
Reputation: 1126
Thanks to @dbrumann who pointed me in right direction, I managed to make a workaround. It's a workaround only as it doesn't explain the redirection itself but avoid it.
I overrided the AbstratcFormLoginAuthenticator::start()
method to this :
/**
* Overrided to avoid redirection loop
* @param Request $request
* @param AuthenticationException|null $authException
*
* @return RedirectResponse|Response
* @throws \Exception
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$url = $this->getLoginUrl();
// If URL different than login one, redirect to login one
if($url !== $this->urlGenerator->generate($request->attributes->get('_route'))) {
return new RedirectResponse($url);
}
// Render login page as a subrequest for this call only
else {
$path['_controller'] = SecurityController::class . '::adminLogin';
$subRequest = $request->duplicate([], null, $path);
return $this->kernel->handle($subRequest, HttpKernelInterface::SUB_REQUEST);
}
}
Redirect to admin/login
page is effective, admin
firewall is calles, admin guard respond and login successful redirect on admin part.
Upvotes: 2