I have an issue with the Authentication Errors when login in my app in Symfony 4.
I can perfectly login with all users, but when I try to fail the login on purpose I don't get any error message neither the $error fills. Here I post the code of my LoginFormAuthenticator and my securityController.
LoginFormAuthenticator
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
/**
* #var UserRepository
*/
private $userRepository;
/**
* #var RouterInterface
*/
private $router;
/**
* #var CsrfTokenManagerInterface
*/
private $csrfTokenManager;
/**
* #var UserPasswordEncoderInterface
*/
private $passwordEncoder;
public function __construct(UserRepository $userRepository, RouterInterface $router, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
{
$this->userRepository = $userRepository;
$this->router = $router;
$this->csrfTokenManager = $csrfTokenManager;
$this->passwordEncoder = $passwordEncoder;
}
public function supports(Request $request)
{
return $request->attributes->get('_route') === 'app_login'
&& $request->isMethod('POST');
}
public function getCredentials(Request $request)
{
$credentials = [
'cargo' => $request->request->get('cargo'),
'password' => $request->request->get('password'),
'csrf_token' => $request->request->get('_csrf_token'),
];
$request->getSession()->set(
Security::LAST_USERNAME,
$credentials['cargo']
);
return $credentials;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
if (!$this->csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}
return $this->userRepository->findOneBy(['cargo' => $credentials['cargo']]);
}
public function checkCredentials($credentials, UserInterface $user)
{
return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
// todo
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
return new RedirectResponse($this->router->generate('app_homepage'));
}
public function start(Request $request, AuthenticationException $authException = null)
{
// todo
}
public function supportsRememberMe()
{
// todo
}
protected function getLoginUrl()
{
return $this->router->generate('app_login');
}
}
login function:
/**
* #Route("/login", name="app_login")
*/
public function login(AuthenticationUtils $authenticationUtils)
{
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig', [
'last_username' => $lastUsername,
'error' => $error,
]);
}
Login HTML twig
<form class="form-signin" method="post">
{% if error %}
<div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
<h3>{{ error.messageKey }}</h3>
{% endif %}
Thanks!
The onAuthenticationFailure method is empty. See parent::onAuthenticationFailure.
Method should returns Response object.
Related
I have a problem with Symfony (4.4) authentication. I want to make an authentication based on the email and the company of the user. This means that you can have several times the same email but with a different company.
I use the following guardAuthenticator for the connection:
<?php
namespace App\Security;
use App\Entity\Company;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
class AppLoginFormAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface
{
use TargetPathTrait;
public const LOGIN_ROUTE = 'app_login';
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 self::LOGIN_ROUTE === $request->attributes->get('_route')
&& $request->isMethod('POST');
}
public function getCredentials(Request $request)
{
$credentials = [
'email' => $request->request->get('_username'),
'slug' => $request->get('slug'),
'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();
}
$company = $this->entityManager->getRepository(Company::class)->findOneBy(['slug' => $credentials['slug']]);
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $credentials['email'], 'company' => $company]);
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']);
}
/**
* Used to upgrade (rehash) the user's password automatically over time.
*/
public function getPassword($credentials): ?string
{
return $credentials['password'];
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
return;
// For example : return new RedirectResponse($this->urlGenerator->generate('some_route'));
throw new \Exception('TODO: provide a valid redirect inside '.__FILE__);
}
protected function getLoginUrl()
{
return $this->urlGenerator->generate(self::LOGIN_ROUTE, ['slug' => $this->request->get('slug')]);
}
}
If I do a dump($user); in getUser or a dump($token, $providerKey); in onAuthenticationSuccess I find the right user. But once the connection is over, when I arrive on another page I am connected with another user with the same email but in a different company.
I think the problem comes from the refresh user that has to be done only on the email, do you know how I can change that?
Thanks to you
You will add company on getcredentials() and use it on the get user function
Try this :
public function getCredentials(Request $request)
{
$company = $this->entityManager->getRepository(Company::class)->findOneBy(['slug' => $request->get('slug')]);
$credentials = [
'email' => $request->request->get('_username'),
'password' => $request->request->get('_password'),
'csrf_token' => $request->request->get('_csrf_token'),
'company' => $company
];
$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'], 'company' => $credentials['company']]);
if (!$user) {
// fail authentication with a custom error
throw new CustomUserMessageAuthenticationException('Email could not be found.');
}
return $user;
}
Symfony 5 login not showing any errors on login page with invalid credential and redirected to login page if it's correct credential.
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(['username' => $credentials['username']]);
if (!$user) {
// fail authentication with a custom error
throw new CustomUserMessageAuthenticationException('Username 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, string $providerKey)
{
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
return new RedirectResponse($this->urlGenerator->generate('reports'));
}
Here is an example of Security Controller
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class SecurityController extends AbstractController
{
/**
* #Route("/login", name="app_login")
* #param AuthenticationUtils $authenticationUtils
* #return Response
*/
public function login(AuthenticationUtils $authenticationUtils): Response
{
if ($this->container->get('security.authorization_checker')->isGranted('ROLE_USER')) {
return $this->redirectToRoute('car.index');
}
if ($this->getUser()) {
return $this->redirectToRoute('car.index');
}
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig', ['last_username' => $lastUsername, 'error' => $error]);
}
/**
* #Route("/logout", name="app_logout")
*/
public function logout()
{
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
}
}
Have followed the instructions on: https://symfony.com/doc/current/security/form_login_setup.html, and in step 3 (in the onAuthenticationSuccess method) - if I leave the Exception in, the profiler bar shows the user logged in, however if I comment in the redirect, the user is lost on the following page. Sessions are set up and working as pdo.
Anyone have any ideas?
LoginFormAuthenticator.php
<?php
namespace App\Security;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
use TargetPathTrait;
private $entityManager;
private $urlGenerator;
private $csrfTokenManager;
public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager)
{
$this->entityManager = $entityManager;
$this->urlGenerator = $urlGenerator;
$this->csrfTokenManager = $csrfTokenManager;
}
public function supports(Request $request)
{
return 'app_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)
{
// Check the user's password or other credentials and return true or false
// If there are no credentials to check, you can just return true
return true;
//throw new \Exception('TODO: check the credentials inside '.__FILE__);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
//throw new \Exception('TODO: provide a valid redirect inside '.__FILE__);
return new RedirectResponse($this->urlGenerator->generate('app_dashboard'));
}
protected function getLoginUrl()
{
return $this->urlGenerator->generate('app_login');
}
}
DashboardController.php
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
class DashboardController extends AbstractController
{
private $session;
function __construct(SessionInterface $session)
{
$this->session = $session;
}
/**
* #Route("/dashboard", name="app_dashboard")
*/
function dashboard()
{
return $this->render('account/dashboard.html.twig', []);
}
}
security.yaml
security:
providers:
users:
entity:
class: 'App\Entity\User'
property: 'email'
encoders:
App\Entity\User:
algorithm: 'auto'
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: lazy
guard:
authenticators:
- App\Security\LoginFormAuthenticator
logout:
path: app_logout
access_control:
- { path: ^/dashboard, roles: ROLE_USER }
User.php
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* User
*
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #UniqueEntity(fields={"email"}, message="There is already an account with this email")
*/
class User implements UserInterface, \Serializable
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="email", type="string", length=2048, nullable=true)
*/
private $email;
/**
* #var string
*
* #ORM\Column(name="password", type="string", length=4096, nullable=false)
*/
private $password;
/**
* #var string
*
* #ORM\Column(name="salt", type="string", length=2048, nullable=true)
*/
private $salt;
////////
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
public function getPassword(): ?string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
public function getSalt(): ?string
{
return $this->salt;
}
public function setSalt(string $salt): self
{
$this->salt = $salt;
return $this;
}
/**
* #inheritDoc
*/
public function getUsername()
{
return $this->email;
}
/**
* #inheritDoc
*/
public function getRoles()
{
return array('ROLE_USER');
}
/**
* #inheritDoc
*/
public function eraseCredentials()
{
}
/**
* #inheritDoc
*/
public function equals(UserInterface $user)
{
return $this->id === $user->getId();
}
/**
* #see \Serializable::serialize()
*/
public function serialize()
{
return serialize(array(
$this->id,
));
}
/**
* #see \Serializable::unserialize()
*/
public function unserialize($serialized)
{
list (
$this->id,
) = unserialize($serialized);
}
public function isEqualTo(UserInterface $user)
{
if (!$user instanceof User) {
return false;
}
if ($this->password !== $user->getPassword()) {
return false;
}
if ($this->salt !== $user->getSalt()) {
return false;
}
if ($this->email !== $user->getEmail()) {
return false;
}
return true;
}
}
Edit..
When the exception is commented in, the logs say:
INFO
19:32:37
request Matched route "app_login".
Show context
INFO
19:32:38
security Guard authentication successful!
Show context
CRITICAL
19:32:38
request Uncaught PHP Exception Exception: "TODO: provide a valid redirect inside /var/www/src/Security/LoginFormAuthenticator.php" at /var/www/src/Security/LoginFormAuthenticator.php line 87
And when the redirect is there the logs say:
19:30:24
security Checking for guard authentication credentials.
Hide context
[▼
"firewall_key" => "main"
"authenticators" => 1
]
19:30:24
security Checking support on guard authenticator.
Hide context
[▼
"firewall_key" => "main"
"authenticator" => "App\Security\LoginFormAuthenticator"
]
19:30:24
security Guard authenticator does not support the request.
Hide context
[▼
"firewall_key" => "main"
"authenticator" => "App\Security\LoginFormAuthenticator"
]
..edit - and same behaviour in both http & https.
I wanted to leave a comment for you, but my reputation is less than 50 so I'll write my comment in the answer.
The support method is deprecated.
Don't use it anymore.
Use the following example:
LoginFormAuthenticator.php
public function getCredentials(Request $request)
{
$isLoginSubmit = 'user_login' === $request->attributes->get('_route') && $request->isMethod('POST');
if(!$isLoginSubmit) {
return;
}
//...other codes
}
protected function getDefaultSuccessRedirectUrl()
{
return $this->urlGenerator->generate('user_dashboard');
}
UserController.php
<?php
namespace AppBundle\Controller\User;
use AppBundle\Form\User\UserManager\UserLoginForm;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Routing\Annotation\Route;
/**
* User controller.
*
* #Route("User")
*/
class UserController extends Controller
{
/**
* #Route("/login", name="user_login")
*/
public function loginAction()
{
$authenticationUtils = $this->get('security.authentication_utils');
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
$form = $this->createForm(UserLoginForm::class, ["_username" => $lastUsername]);
return $this->render('User/login.html.twig', [
'form' => $form->createView(),
'error' => $error,
]);
}
/**
* #Route("/logout", name="user_logout")
*/
public function logoutAction()
{
throw new \Exception('User logout');
}
/**
* #Route("/dashboard", name="user_dashboard")
*/
public function dashboardAction()
{
return $this->render('User/dashboard.html.twig');
}
}
security.yaml
Provider and pattern is required:
firewalls:
//...other firewalls
main:
anonymous: ~
pattern: ^/user
provider: users
access_control:
- { path: ^/user/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/user, roles: ROLE_USER }
Hope to help you.
Leave a comment with any feedback.
The problem was that the user wasn't being refreshed, the Guard Authenticator was doing exactly what it should. Stripping back the User Entity to the following fixed it.
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #UniqueEntity(fields={"email"}, message="There is already an account with this email")
*/
class User implements UserInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255, unique=true)
*/
private $email;
/**
* #ORM\Column(type="string", length=255)
*/
private $password;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
public function getUsername()
{
return $this->email;
}
public function getPassword(): ?string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
////
public function getRoles()
{
return [
'ROLE_USER'
];
}
public function getSalt()
{
}
public function eraseCredentials()
{
}
}
i'm setting up a login system, witch relies on a curl call to another web service to authenticate the user through a login form, for that i followed the documentation by using the maker bundle to generate a custom user provider, a User model, a SecurityController and a LoginFormAthenticator.
the form shows up but don't submit and give no error to help me out for debugging.
according to symfony documentation the support method of LoginFormAthenticator is called each time even before the controllers, indeed when i write a die in it it shows what i put in the die.
besides that, i tried to do it manually by creating the form in the login action and check if submit but the form doesn't submit.
i've been struggling with it since 3 days.
Login form
{% extends 'base.html.twig' %}
{% block stylesheets %}
{{ parent() }}
{{ encore_entry_link_tags('login') }}
{% endblock %}
{% block body %}
<form class="form-signin" method="post" action="{{ path('app_login') }}">
{% if error %}
<div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}
<h1 class="h3 mb-3 font-weight-normal">Connectez vous</h1>
<label for="inputEmail" class="sr-only">Email</label>
<input type="email" value="" name="email" id="inputEmail" class="form-control" placeholder="Email" required autofocus>
<label for="inputPassword" class="sr-only">Mot de passe</label>
<input type="password" name="password" id="inputPassword" class="form-control" placeholder="Mot de passe" required>
<input type="hidden" name="_csrf_token"
value="{{ csrf_token('authenticate') }}"
>
<button class="btn btn-lg btn-primary" type="submit">
Connexion
</button>
</form>
{% endblock %}
Security.yaml
security:
providers:
app_user_provider:
id: App\Security\UserProvider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
logout: ~
guard:
authenticators:
- App\Security\LoginFormAuthenticator
access_control:
- { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
SecurityController.php
class SecurityController extends AbstractController
{
/**
* #param AuthenticationUtils $authenticationUtils
* #return Response
* #Route("/login", name="app_login")
*/
public function login(AuthenticationUtils $authenticationUtils): Response
{
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig', [
'last_username' => $lastUsername,
'error' => $error
]);
}
}
User Model (since i'm not using Doctrine)
class User implements UserInterface
{
private $email;
private $roles = [];
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
/**
* A visual identifier that represents this user.
*
* #see UserInterface
*/
public function getUsername(): string
{
return (string) $this->email;
}
/**
* #see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* #see UserInterface
*/
public function getPassword()
{
// not needed for apps that do not check user passwords
}
/**
* #see UserInterface
*/
public function getSalt()
{
// not needed for apps that do not check user passwords
}
/**
* #see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
}
UserProvider
class UserProvider implements UserProviderInterface
{
public function loadUserByUsername($username)
{
// Load a User object from your data source or throw UsernameNotFoundException.
// The $username argument may not actually be a username:
// it is whatever value is being returned by the getUsername()
// method in your User class.
throw new \Exception('TODO: fill in loadUserByUsername() inside
'.__FILE__);
}
public function refreshUser(UserInterface $user)
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Invalid user class
"%s".', get_class($user)));
}
// Return a User object after making sure its data is "fresh".
// Or throw a UsernameNotFoundException if the user no longer exists.
throw new \Exception('TODO: fill in refreshUser() inside '.__FILE__);
}
public function supportsClass($class)
{
return User::class === $class;
}
}
LoginFormAthenticator this is class is supposed to handle every action of the login system
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
use TargetPathTrait;
private $urlGenerator;
private $csrfTokenManager;
public function __construct(UrlGeneratorInterface $urlGenerator,
CsrfTokenManagerInterface $csrfTokenManager)
{
$this->urlGenerator = $urlGenerator;
$this->csrfTokenManager = $csrfTokenManager;
}
public function supports(Request $request)
{
return 'app_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();
}
// Load / create our user however you need.
// You can do this by calling the user provider, or with custom logic here.
$user = $userProvider->loadUserByUsername($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)
{
// Check the user's password or other credentials and return true or false
// If there are no credentials to check, you can just return true
throw new \Exception('TODO: check the credentials inside '.__FILE__);
}
public function onAuthenticationSuccess(Request $request,
TokenInterface $token, $providerKey)
{
if ($targetPath = $this->getTargetPath($request->getSession(),
$providerKey)) {
return new RedirectResponse($targetPath);
}
// For example : return new RedirectResponse($this->urlGenerator-
>generate('some_route'));
throw new \Exception('TODO: provide a valid redirect inside
'.__FILE__);
}
protected function getLoginUrl()
{
return $this->urlGenerator->generate('app_login');
}
}
So my code is pretty straightforward, I have a login form, an authenticator, and a login method in the controller.
In my authenticator class, when user credentials are wrong, I store the email used in the session. See below ( specifically the getCredentials method ) :
<?php
namespace AppBundle\Security;
use Psr\Log\LoggerInterface;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
use AppBundle\Form\LoginFormType;
class LoginFormAuthenticator extends AbstractGuardAuthenticator
{
private $passwordEncoder;
private $logger;
private $router;
private $formFactory;
public function __construct(FormFactoryInterface $formFactory, LoggerInterface $logger,UserPasswordEncoderInterface $passwordEncoder, RouterInterface $router)
{
$this->formFactory = $formFactory;
$this->router = $router;
$this->logger = $logger;
$this->passwordEncoder = $passwordEncoder;
}
public function supports(Request $request)
{
return ($request->getPathInfo() == '/login' && $request->isMethod('POST'));
}
public function getCredentials(Request $request)
{
$form = $this->formFactory->create(LoginFormType::class);
$form->handleRequest($request);
$data = $form->getData();
**$request->getSession()->set(
Security::LAST_USERNAME,
$data['email']
);**
$test = $request->getSession()->get(Security::LAST_USERNAME);
$this->logger->info('email : '.$test);
return $data;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$email = $credentials['email'];
$this->logger->info('email getUser : '.$email);
if (null === $email) {
return;
}
return $userProvider->loadUserByUsername($email);
}
public function checkCredentials($credentials, UserInterface $user)
{
$plainPassword = $credentials['password'];
$encoder = $this->passwordEncoder;
if(!$encoder->isPasswordValid($user, $plainPassword)) {
return new Response('Password Invalid');
}
// return true to cause authentication success
return true;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// on success, let the request continue
return new Response("<html><head></head><body>SUCCESS</body></html>");
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
$request->getSession()->set(Security::LAST_USERNAME, $request->get('email'));
$response = new RedirectResponse($this->router->generate('security_login'));
}
/**
* Called when authentication is needed, but it's not sent
*/
public function start(Request $request, AuthenticationException $authException = null)
{
return new Response('Authentication Required');
}
public function supportsRememberMe()
{
return false;
}
}
So far so good, the email is stored in the session. ( See the getCredentials method ).
However, the same value stored in the session is empty when accessed from the login controller below :
/**
* #Route("/login", name="security_login")
*/
public function loginAction(Request $request, AuthenticationUtils $authUtils)
{
$error = $authUtils->getLastAuthenticationError();
$last_username = $authUtils->getLastUsername();
$logger->info('email in SESSION : '.$last_username);
$form = $this->createForm(LoginFormType::class,[
'email' => $last_username
]);
return $this->render('security/login.html.twig', [
'loginForm' => $form->createView(),
'error'=> $error
]);
}
It's just empty, so I'm not sure what's happening here or what I'm doing wrong.
Please advise.
Thanks!