I have tried implementing a simple login form with both Symfony Guard and Symfony Authentication provider but despite everything I tried, both variables $last_email and $error are always empty.
I have followed this step by step: https://symfony.com/doc/4.4/security/form_login_setup.html and all of my LoginFormAuthenticator.php is identical. So is my controller and login.html.twig
public function login(AuthenticationUtils $authenticationUtils, Request $request, AuthorizationCheckerInterface $authChecker): Response
{
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
dump($error);
// last username entered by the user
$last_email = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig', [
'last_email' => $last_email,
'error' => $error,
]);
}
Here, even if the email does not exist or the password is invalid, $error is always null. Why?
Here are the logs:
[2020-08-23 11:53:35] request.INFO: Matched route "login". {"route":"login","route_parameters":{"_route":"login","_controller":"App\Controller\SecurityController::login"},"request_uri":"http://localhost:8000/login","method":"POST"} [2020-08-23 11:53:35] security.DEBUG: Checking for guard authentication credentials. {"firewall_key":"main","authenticators":1} [2020-08-23 11:53:35] security.DEBUG: Checking support on guard authenticator. {"firewall_key":"main","authenticator":"App\Security\LoginFormAuthenticator"} [2020-08-23 11:53:35] security.DEBUG: Calling getCredentials() on guard authenticator. {"firewall_key":"main","authenticator":"App\Security\LoginFormAuthenticator"} [2020-08-23 11:53:35] security.DEBUG: Passing guard token information to the GuardAuthenticationProvider {"firewall_key":"main","authenticator":"App\Security\LoginFormAuthenticator"} [2020-08-23 11:53:35] security.INFO: Guard authentication failed. {"exception":"[object] (Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException(code: 0): at /src/Security/LoginFormAuthenticator.php:68)","authenticator":"App\Security\LoginFormAuthenticator"} [2020-08-23 11:53:35] security.DEBUG: The "App\Security\LoginFormAuthenticator" authenticator set the response. Any later authenticator will not be called {"authenticator":"App\Security\LoginFormAuthenticator"} [2020-08-23 11:53:35] request.INFO: Matched route "login". {"route":"login","route_parameters":{"_route":"login","_controller":"App\Controller\SecurityController::login"},"request_uri":"http://localhost:8000/login","method":"GET"} [2020-08-23 11:53:35] security.DEBUG: Checking for guard authentication credentials. {"firewall_key":"main","authenticators":1} [2020-08-23 11:53:35] security.DEBUG: Checking support on guard authenticator. {"firewall_key":"main","authenticator":"App\Security\LoginFormAuthenticator"} [2020-08-23 11:53:35] security.DEBUG: Guard authenticator does not support the request. {"firewall_key":"main","authenticator":"App\Security\LoginFormAuthenticator"} [2020-08-23 11:53:35] security.INFO: Populated the TokenStorage with an anonymous Token.
My Security.yaml
security:
access_denied_url: /
encoders:
App\Entity\User:
algorithm: bcrypt
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
in_memory: { memory: ~ }
our_db_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
http_basic: ~
provider: our_db_provider
anonymous: ~
form_login:
use_referer: true
csrf_token_generator: security.csrf.token_manager
remember_me:
secret: '%kernel.secret%'
lifetime: 604800 # 1 week in seconds
path: /
name: REMEMBERME
remember_me_parameter: _remember_me
logout:
path: /logout
target: /
guard:
authenticators:
- App\Security\LoginFormAuthenticator
# activate different ways to authenticate
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#firewalls-authentication
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
role_hierarchy:
ROLE_ADMIN: [ROLE_USER, ROLE_INVESTOR, ROLE_STARTUP]
ROLE_INVESTOR: [ROLE_USER]
ROLE_STARTUP: [ROLE_USER]
access_control:
- { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
My LoginFormAuthenticator.php
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
use TargetPathTrait;
public const LOGIN_ROUTE = 'login';
private $entityManager;
private $urlGenerator;
private $csrfTokenManager;
private $passwordEncoder;
private $flash;
public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder, FlashBagInterface $flash)
{
$this->entityManager = $entityManager;
$this->urlGenerator = $urlGenerator;
$this->csrfTokenManager = $csrfTokenManager;
$this->passwordEncoder = $passwordEncoder;
$this->flash = $flash;
}
public function supports(Request $request)
{
return self::LOGIN_ROUTE === $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']]);
dump($user);
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)
{
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
return new RedirectResponse($this->urlGenerator->generate('logged_in'));
}
protected function getLoginUrl()
{
return $this->urlGenerator->generate(self::LOGIN_ROUTE);
}
}
Any help is much appreciated. I'm using Symfony 4.4
EDIT: So the login form WORKS - I can log in and log out. But if my email doesn't exist or if my credentials are invalid, no error message get displayed. Doing a var_dump on $error and $last_email returns NULL and empty string, always. Also, I tried to var dump $this->passwordEncoder->isPasswordValid($user, $credentials['password']); from my LoginFormAuthenticator in the checkCredentials method and if my credentials are invalid, i can see it returns false. So that works. But somehow, something seems broken in the background.
Matched route "login"at the same time. That can be a problem but not the solution. You should go deeperAuthenticationUtilsto debug and get more information about where the problem can be. - S.LT$authenticationUtils->getLastAuthenticationError();returns the last exception thrown in your GuardAuthenticator. So you either not throw any exception or the redirect makes you lose the data - TZieburaGuard authenticator does not support the request? - S.LT