1
votes

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');
    }
}

2
What have you tried to debug the problem? - Nico Haase
I can not find the problem - lulu
And what have you tried to find it? - Nico Haase
Start with the basics in your authenticator. Does the constructor get called? Does supports get called? - Cerad
you could also try to remove the credentials checks temporarily and just return one user from getUser. What happens then? - olidem

2 Answers

1
votes

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);
    }
0
votes
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