From symfony 4, I would like create a simple authentication form. I created a User class (the identifier is the email field, not the 'Username', I created a class controller and configured the security.yml file.
But when I go to the form page and try to login (fill email/password fields and click on the submit button), I get the error "Invalid credentials."
security.yaml
encoders:
App\Entity\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\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
guard:
authenticators:
- App\Security\LoginFormAuthenticator
logout:
path: /logout
#ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
# 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, roles: ROLE_ADMIN }
- { path: ^/profile, roles: ROLE_USER }
user.php
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
*/
class User implements UserInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=180, unique=true)
*/
private $email;
/**
* #ORM\Column(type="json")
*/
private $roles = [];
/**
* #var string The hashed password
* #ORM\Column(type="string",length=255)
*/
private $password;
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;
}
/**
* 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(): string
{
return (string) $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* #see UserInterface
*/
public function getSalt()
{
// not needed when using the "bcrypt" algorithm in security.yaml
}
/**
* #see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
public function getNom(): ?string
{
return $this->nom;
}
public function setNom(string $nom): self
{
$this->nom = $nom;
return $this;
}
public function getPrenom(): ?string
{
return $this->prenom;
}
public function setPrenom(string $prenom): self
{
$this->prenom = $prenom;
return $this;
}
public function getAdresse(): ?string
{
return $this->adresse;
}
public function setAdresse(?string $adresse): self
{
$this->adresse = $adresse;
return $this;
}
public function getTelephone(): ?string
{
return $this->telephone;
}
public function setTelephone(?string $telephone): self
{
$this->telephone = $telephone;
return $this;
}
public function getSexe(): ?string
{
return $this->sexe;
}
public function setSexe(?string $sexe): self
{
$this->sexe = $sexe;
return $this;
}
/**
* #return Collection|Eleve[]
*/
public function getElevesUser1(): Collection
{
return $this->elevesUser1;
}
public function addElevesUser1(Eleve $elevesUser1): self
{
if (!$this->elevesUser1->contains($elevesUser1)) {
$this->elevesUser1[] = $elevesUser1;
$elevesUser1->setUsertuteur1($this);
}
return $this;
}
public function removeElevesUser1(Eleve $elevesUser1): self
{
if ($this->elevesUser1->contains($elevesUser1)) {
$this->elevesUser1->removeElement($elevesUser1);
// set the owning side to null (unless already changed)
if ($elevesUser1->getUsertuteur1() === $this) {
$elevesUser1->setUsertuteur1(null);
}
}
return $this;
}
/**
* #return Collection|Eleve[]
*/
public function getElevesuser2(): Collection
{
return $this->elevesuser2;
}
public function addElevesuser2(Eleve $elevesuser2): self
{
if (!$this->elevesuser2->contains($elevesuser2)) {
$this->elevesuser2[] = $elevesuser2;
$elevesuser2->setUsertuteur2($this);
}
return $this;
}
public function removeElevesuser2(Eleve $elevesuser2): self
{
if ($this->elevesuser2->contains($elevesuser2)) {
$this->elevesuser2->removeElement($elevesuser2);
// set the owning side to null (unless already changed)
if ($elevesuser2->getUsertuteur2() === $this) {
$elevesuser2->setUsertuteur2(null);
}
}
return $this;
}
/**
* #return Collection|profClasse[]
*/
public function getProfsClasse(): Collection
{
return $this->profsClasse;
}
public function addProfsClasse(profClasse $profsClasse): self
{
if (!$this->profsClasse->contains($profsClasse)) {
$this->profsClasse[] = $profsClasse;
$profsClasse->setUser($this);
}
return $this;
}
public function removeProfsClasse(profClasse $profsClasse): self
{
if ($this->profsClasse->contains($profsClasse)) {
$this->profsClasse->removeElement($profsClasse);
// set the owning side to null (unless already changed)
if ($profsClasse->getUser() === $this) {
$profsClasse->setUser(null);
}
}
return $this;
}
public function getCategorie(): ?Categorie
{
return $this->categorie;
}
public function setCategorie(?Categorie $categorie): self
{
$this->categorie = $categorie;
return $this;
}
}
securityController
<?php
namespace App\Controller;
use App\Entity\User;
use App\Security\LoginFormAuthenticator;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityManager;
use App\Form\RegistrationType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class SecurityController extends AbstractController
{
/**
* #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]);
}
/**
* #Route("/registration", name="registration")
*/
public function registration(Request $request, EntityManagerInterface $manager,GuardAuthenticatorHandler $guardAuthenticatorHandler,LoginFormAuthenticator $loginFormAuthenticator, UserPasswordEncoderInterface $passwordEncoder)
{
//obtenir une instance de User (vide)
$user = new User();
//relier l'instance $user avec notre formulaire pour la remplir
$form = $this->createForm(RegistrationType::class,$user);
// analyser la requête HTTP
if ($form->handleRequest($request)->isSubmitted() && $form->isValid()) {
$passwordEncoder = $passwordEncoder->encodePassword($user, $user->getPassword());
$user->setPassword($passwordEncoder);
$user->setRoles(['ROLE_ADMIN']);
$manager->persist($user);
$manager->flush();
//une fois inscription terminé, redirection vers la page login
return $guardAuthenticatorHandler->authenticateUserAndHandleSuccess($user,
$request,
$loginFormAuthenticator,
'main');
}
//je veux afficher ce fichier là et j'aimerais lui passer des variables
//qu'il pourra utiliser
return $this->render('security/registration.html.twig', [
'form' => $form->createView()
]);
}
registrationType
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
class RegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email',EmailType::class)
->add('password', RepeatedType::class,['type' => PasswordType::class,
'invalid_message' =>'Les mots de passes doivent être identiques',
'required'=>true,
'first_options'=>['label'=>'mot de passe'],
'second_options'=>['label'=>'repeter mot de passe'],])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
login.html.twig
{% extends 'base.html.twig' %}
{% block title %}Log in!{% endblock %}
{% block body %}
<form method="post">
{% if error %}
<div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}
<h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
<label for="inputEmail" class="sr-only">Email</label>
<input type="email" value="{{ last_username }}" name="email" id="inputEmail" class="form-control" placeholder="Email" required autofocus>
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password" required>
<input type="hidden" name="_csrf_token"
value="{{ csrf_token('authenticate') }}"
>
{#
Uncomment this section and add a remember_me option below your firewall to activate remember me functionality.
See https://symfony.com/doc/current/security/remember_me.html
<div class="checkbox mb-3">
<label>
<input type="checkbox" name="_remember_me"> Remember me
</label>
</div>
#}
<button class="btn btn-lg btn-primary" type="submit">
Sign in
</button>
</form>
{% endblock %}
loginFormAuthenticator
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\Encoder\UserPasswordEncoderInterface;
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;
private $passwordEncoder;
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)
//check si on est bien sur la page de login
{
return 'app_login' === $request->attributes->get('_route')
&& $request->isMethod('POST');
}
public function getCredentials(Request $request)
{//recuperer les infos soumises par l'utilisateur
$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)
{//on recupère d'abord le token et on verifie sil est valable'
$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)
{//elle va aller prendre le password entré par le user et va le comparer
return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{//une fois que l'on est bien connecté
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
return new RedirectResponse($this->urlGenerator->generate('home'));
}
protected function getLoginUrl()
{
return $this->urlGenerator->generate('app_login');
}
}
The problem might be located in your LoginFormAuthenticator.
The guard authentication system relies on such a class, it is described here
Also, you can have a symfony form to create the login page instead of your "good old html form". So instead of defining your plain html, you can just output the form as any other form via a standard form.html.twig
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
and in your controller
/**
* #Route("/login", name="security-login")
*/
public function login(Request $request, AuthenticationUtils $authenticationUtils): Response
{
if($this->isGranted('IS_AUTHENTICATED_FULLY'))
return $this->redirectToRoute('index');
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
$data = [];
$builder = $this->createFormBuilder($data, ['csrf_token_id' => 'authenticate']);
$builder->add('username', TextType::class,[
'label' => 'Benutzername'
])->add('password', PasswordType::class, [
'label' => "Passwort"
])->add('save', SubmitType::class, [
'label' => 'Anmelden'
]);
$response = new Response('');
$form = $builder->getForm();
if($error){
$response->setStatusCode(401);
if($error instanceof BadCredentialsException){
$form->get('password')->addError(new FormError("Das Passwort ist falsch."));
} else if( $error instanceof CustomUserMessageAuthenticationException ){
/** #see CustomAuthenticator::getUser() */
if($error->getCode() === 1)
$form->get('username')->addError(new FormError("Der Nutzer existiert, ist aber deaktiviert. Kontaktieren Sie einen Administrator."));
else if($error->getCode() === 0)
$form->get('username')->addError(new FormError("Der Nutzer ist unbekannt."));
} else {
$form->get('user')->addError(new FormError("Unerwarteter Fehler. Bitte kontaktieren Sie den risklytics-Support."));
}
}
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
}
return $this->render('default/form.html.twig', [
'form' => $form->createView(),
'title' => 'Anmelden'], $response);
}
encoders:
App\Entity\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\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
guard:
authenticators:
- App\Security\LoginFormAuthenticator
logout:
path: /logout
#ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
# 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, roles: ROLE_ADMIN }
- { path: ^/profile, roles: ROLE_USER }
You've declared a provider app_user_provider: but it's not used anywhere inside your firewalls. So, in main:, try to add:
provider: app_user_provider
And if you encounter another problem, try to enable the form_login:
form_login:
check_path: LOGIN_PATH
login_path: LOGIN_PATH
Related
This question already has an answer here:
Symfony simple Login form with the new authentication method not working
(1 answer)
Closed 1 year ago.
Following an upgrade from Symfony 4 to the latest Symfony 5 version, I've tried upgrading my registration and login form guard thingies to become authenticator thingies.
After trying a lot of stuff, and using the make:user and make:auth commands again and again, I'm now in a situation where I have no warning, no error, just when I try to log in, nothing happens: it doesn't matter whether the username/password combination is correct or not, I just get back to the login form.
Here are the files I believe could be relevant to the issue:
config/packages/security.yaml
security:
enable_authenticator_manager: true
password_hashers:
App\Entity\Player:
algorithm: auto
providers:
app_user_provider:
entity:
class: App\Entity\Player
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider
custom_authenticator: App\Security\LoginFormAuthenticator
logout:
path: app_logout
remember_me:
secret: '%kernel.secret%'
lifetime: 604800
path: /
always_remember_me: true
switch_user: true
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
src/Controller/SecurityController.php
<?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")
*/
public function login(AuthenticationUtils $authenticationUtils): Response
{
// If the user is logged in, redirect them to their profile page.
if ($this->getUser()) {
return $this->redirectToRoute('player_control_panel');
}
// 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.');
}
}
src/Entity/Player.php
<?php
namespace App\Entity;
use App\Repository\PlayerRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* #ORM\Entity(repositoryClass=PlayerRepository::class)
* #UniqueEntity(fields={"email"}, message="There is already an account with this email")
*/
class Player implements UserInterface, PasswordAuthenticatedUserInterface
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=180, unique=true)
*/
private $email;
/**
* #ORM\Column(type="json")
*/
private $roles = [];
/**
* #var string The hashed password
* #ORM\Column(type="string")
*/
private $password;
/**
* #ORM\Column(type="boolean")
*/
private $isVerified = false;
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;
}
/**
* A visual identifier that represents this user.
*
* #see UserInterface
*/
public function getUserIdentifier(): string
{
return (string) $this->email;
}
/**
* #deprecated since Symfony 5.3, use getUserIdentifier instead
*/
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 PasswordAuthenticatedUserInterface
*/
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* Returning a salt is only needed, if you are not using a modern
* hashing algorithm (e.g. bcrypt or sodium) in your security.yaml.
*
* #see UserInterface
*/
public function getSalt(): ?string
{
return null;
}
/**
* #see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
public function isVerified(): bool
{
return $this->isVerified;
}
public function setIsVerified(bool $isVerified): self
{
$this->isVerified = $isVerified;
return $this;
}
}
src/Security/LoginFormAuthenticator.php
<?php
namespace App\Security;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
use TargetPathTrait;
public const LOGIN_ROUTE = 'app_login';
private UrlGeneratorInterface $urlGenerator;
public function __construct(UrlGeneratorInterface $urlGenerator)
{
$this->urlGenerator = $urlGenerator;
}
public function authenticate(Request $request): PassportInterface
{
$email = $request->request->get('email', '');
$request->getSession()->set(Security::LAST_USERNAME, $email);
return new Passport(
new UserBadge($email),
new PasswordCredentials($request->request->get('password', '')),
[
new CsrfTokenBadge('authenticate', $request->request->get('_csrf_token')),
]
);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
$request->getSession()->getFlashBag()->add('success', "You are now signed in. Greetings, commander.");
if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) {
return new RedirectResponse($targetPath);
}
return new RedirectResponse($this->urlGenerator->generate('app_index'));
}
protected function getLoginUrl(Request $request): string
{
return $this->urlGenerator->generate(self::LOGIN_ROUTE);
}
}
I just can't see what's going wrong, if you have an idea, or even if you can just give me an idea of how I could get an idea of what's going wrong, I'd appreciate it. Thank you!
Sorry, that's a duplicate of Symfony simple Login form with the new authentication method not working which I found a few minutes after posting this (I swear I searched before and didn't find it).
The issue is indeed the supports() method missing in the authenticator class, the one in the abstract login form authenticator apparently isn't sufficient. Adding that method fixed it:
in src/Security/LoginFormAuthenticator.php
public function supports(Request $request): bool
{
return self::LOGIN_ROUTE === $request->attributes->get('_route')
&& $request->isMethod('POST');
}
Full file:
<?php
namespace App\Security;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
use TargetPathTrait;
public const LOGIN_ROUTE = 'app_login';
private UrlGeneratorInterface $urlGenerator;
public function __construct(UrlGeneratorInterface $urlGenerator)
{
$this->urlGenerator = $urlGenerator;
}
public function supports(Request $request): bool
{
return self::LOGIN_ROUTE === $request->attributes->get('_route')
&& $request->isMethod('POST');
}
public function authenticate(Request $request): PassportInterface
{
$email = $request->request->get('email', '');
$request->getSession()->set(Security::LAST_USERNAME, $email);
return new Passport(
new UserBadge($email),
new PasswordCredentials($request->request->get('password', '')),
[
new CsrfTokenBadge('authenticate', $request->request->get('_csrf_token')),
]
);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
$request->getSession()->getFlashBag()->add('success', "You are now signed in. Greetings, commander.");
if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) {
return new RedirectResponse($targetPath);
}
return new RedirectResponse($this->urlGenerator->generate('app_index'));
}
protected function getLoginUrl(Request $request): string
{
return $this->urlGenerator->generate(self::LOGIN_ROUTE);
}
}
I am working with Symfony 5 with Easyadmin 3, but I have a problem.
Using Security Bundle and a make: auth, i set up a login and I can log in normally, the problem is that once I log out, I can't log in again with the same user unless i edit it.
src/Security/LoginFormAuthenticator:
<?php
namespace App\Security;
use App\Entity\Persona;
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 LoginFormAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface
{
use TargetPathTrait;
public const LOGIN_ROUTE = 'app_login';
private $entityManager;
private $urlGenerator;
private $csrfTokenManager;
private $passwordEncoder;
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)
{
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(Persona::class)->findOneBy(['email' => $credentials['email']]);
if (!$user) {
// fail authentication with a custom error
throw new CustomUserMessageAuthenticationException('No se encontro cuenta con ese Email.');
}
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, string $providerKey)
{
if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
return new RedirectResponse($targetPath);
}
// For example : return new RedirectResponse($this->urlGenerator->generate('some_route'));
return new RedirectResponse('admin'); //Ni bien se loguee donde ira
}
protected function getLoginUrl()
{
return $this->urlGenerator->generate(self::LOGIN_ROUTE);
}
}
src/Controller/SecurityController:
<?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")
*/
public function login(AuthenticationUtils $authenticationUtils): Response
{
// if ($this->getUser()) {
// return $this->redirectToRoute('target_path');
// }
// 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.');
}
}
Config/Package/Security.yaml
security:
encoders:
Symfony\Component\Security\Core\User\User: bcrypt
App\Entity\Persona:
algorithm: auto
# 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\Persona
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
lazy: true
provider: app_user_provider
guard:
authenticators:
- App\Security\LoginFormAuthenticator
logout:
path: app_logout
# where to redirect after logout
target: /login
#target: app_any_route
# 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
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
I am trying to build a login page register user its working after I wanted to login them in so I used the make: Auth seemed but now Symfony 5 keeps redirecting from the register route to login without saving the users in the DB first when the form is submitted it was working before i ran the make auth cmd
most of the code regaring the user auth
this is the guide i used https://symfony.com/doc/current/security/form_login_setup.html
on the Symfony docs
i guess that's why it messing up
When you submit the form, the LoginFormAuthenticator will intercept
the request, read the email (or whatever field you’re using) &
password from the form, find the User object, validate the CSRF token
and check the password
security:
encoders:
App\Entity\User:
algorithm: auto
providers:
users:
entity:
class: App\Entity\User
property: Username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
access_denied_handler: App\Security\AccessDeniedHandler
anonymous: false
lazy: true
provider: users
form_login:
login_path: app_login
check_path: app_login
guard:
authenticators:
- App\Security\AuthAuthenticator
logout:
path: app_logout
# where to redirect after logout
# target: app_any_route
# 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
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
`
<?php
namespace App\Controller;
use App\Entity\User;
use App\Form\RegistrationFormType;
use App\Security\EmailVerifier;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mime\Address;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;
class RegistrationController extends AbstractController
{
private $emailVerifier;
public function __construct(EmailVerifier $emailVerifier)
{
$this->emailVerifier = $emailVerifier;
}
/**
* #Route("/register", name="app_register")
*/
public function register(Request $request, UserPasswordEncoderInterface $passwordEncoder): Response
{
$user = new User();
$form = $this->createForm(RegistrationFormType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// encode the plain password
$user->setPassowrd(
$passwordEncoder->encodePassword(
$user,
$form->get('plainPassword')->getData()
)
);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($user);
$entityManager->flush();
// generate a signed url and email it to the user
$this->emailVerifier->sendEmailConfirmation('app_verify_email', $user,
(new TemplatedEmail())
->from(new Address('root#root.com', 'root'))
->to($user->getEmail())
->subject('Please Confirm your Email')
->htmlTemplate('registration/confirmation_email.html.twig')
);
// do anything else you need here, like send an email
return $this->redirectToRoute('index');
}
return $this->render('registration/register.html.twig', [
'registrationForm' => $form->createView(),
]);
}
/**
* #Route("/verify/email", name="app_verify_email")
*/
public function verifyUserEmail(Request $request): Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
// validate email confirmation link, sets User::isVerified=true and persists
try {
$this->emailVerifier->handleEmailConfirmation($request, $this->getUser());
} catch (VerifyEmailExceptionInterface $exception) {
$this->addFlash('verify_email_error', $exception->getReason());
return $this->redirectToRoute('app_register');
}
// #TODO Change the redirect on success and handle or remove the flash message in your templates
$this->addFlash('success', 'Your email address has been verified.');
return $this->redirectToRoute('app_register');
}
}
<?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")
*/
public function login(AuthenticationUtils $authenticationUtils): Response
{
if ($this->getUser()) {
return $this->redirectToRoute('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.');
}
}
{% extends 'base.html.twig' %}
{% block title %}Register{% endblock %}
{% block body %}
{% for flashError in app.flashes('verify_email_error') %}
<div class="alert alert-danger" role="alert">{{ flashError }}</div>
{% endfor %}
<h1>Register</h1>
{{ form_start(registrationForm) }}
{{ form_row(registrationForm.Username) }}
{{ form_row(registrationForm.plainPassword, {
label: 'Password'
}) }}
{{ form_row(registrationForm.agreeTerms) }}
<button type="submit" class="btn">Register</button>
{{ form_end(registrationForm) }}
{% endblock %}
<?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\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 AuthAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface
{
use TargetPathTrait;
public const LOGIN_ROUTE = 'app_login';
private $entityManager;
private $urlGenerator;
private $csrfTokenManager;
private $passwordEncoder;
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)
{
return self::LOGIN_ROUTE === $request->attributes->get('_route')
&& $request->isMethod('POST');
}
public function getCredentials(Request $request)
{
$credentials = [
'Username' => $request->request->get('Username'),
'password' => $request->request->get('password'),
'csrf_token' => $request->request->get('_csrf_token'),
];
$request->getSession()->set(
Security::LAST_USERNAME,
$credentials['Username']
);
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(['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']);
}
/**
* 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);
}
// 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);
}
}
You seem to lack the csrf_token hidden field on your twig form as is explained on the Symfony doc link you provided : How to build a login form
<input type="hidden" name="_csrf_token"
value="{{ csrf_token('authenticate') }}"
>
Although i wouldn't recommend it, another solution if your application is not critical would be to disable csrf_checking - just remove all mentions of CSRF from your AuthAuthenticator.
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');
}
}