Create Entity named User
email; } public function getRoles() { return ['ROLE_USER']; } public function getPassword() { return $this->password; } public function getSalt() { } public function eraseCredentials() { $this->plainPassword = null; } /** * @param mixed $email */ public function setEmail($email) { $this->email = $email; } /** * @param mixed $password */ public function setPassword($password) { $this->password = $password; } /** * @return mixed */ public function getPlainPassword() { return $this->plainPassword; } /** * @param mixed $plainPassword */ public function setPlainPassword($plainPassword) { $this->plainPassword = $plainPassword; //This should be here to set value for password to activate doctrine listeners $this->password = null; } }
and generate migration:
#php bin/console doctrine:migrations:diff #php bin/console doctrine:migrations:migrate
Now we need login form
Create class named SecurityController
getLastAuthenticationError(); // last username entered by the user $lastUsername = $authUtils->getLastUsername(); $form = $this->createForm(LoginForm::class, [ '_username' => $lastUsername ]); return $this->render('security/login.html.twig', array( 'form' => $form->createView(), 'error' => $error, )); } /** * @Route("/logout", name="security_logout") */ public function logoutAction() { throw new \Exception('this should not be reached!'); } }
We need LoginForm class:
add('_username') ->add('_password', PasswordType::class) ; } }
and than create login.html.twig in views/security folder.
{% extends 'base.html.twig' %} {% block body %}{% endblock %}Login
{% if error %}{{ error.messageKey|trans(error.messageData, 'security') }}{% endif %} {{ form_start(form) }} {{ form_row(form._username) }} {{ form_row(form._password) }} {{ form_end(form) }}
Now you can try it in your browser -> /login
It’s time for AUTHENTICATOR
We will use GUARD for our purposes. Create new class named LoginFormAuthenticator in AppBundle/Security folder:
formFactory = $formFactory; $this->em = $em; $this->router = $router; $this->passwordEncoder = $passwordEncoder; } public function getCredentials(Request $request) { $isLoginSubmit = $request->getPathInfo() == '/login' && $request->isMethod('POST'); if(!$isLoginSubmit){ return; } $form = $this->formFactory->create(LoginForm::class); $form->handleRequest($request); $data = $form->getData(); //To prepopulate _username field $request->getSession()->set( Security::LAST_USERNAME, $data['_username'] ); return $data; } public function getUser($credentials, UserProviderInterface $userProvider) { $username = $credentials['_username']; return $this->em->getRepository('AppBundle:User') ->findOneBy([ 'email' => $username ]); } public function checkCredentials($credentials, UserInterface $user) { $password = $credentials['_password']; if($this->passwordEncoder->isPasswordValid($user, $password)){ return true; } return false; } protected function getLoginUrl() { return $this->router->generate('security_login'); } /** * Redirect to default page if there is no previous page * @return mixed */ protected function getDefaultSuccessRedirectUrl() { return $this->router->generate('homepage'); } }
Next thing we need to do is to register our authenticator as a service. Go to services.yml and add to services:
app.security.login_form_authenticator: class: AppBundle\Security\LoginFormAuthenticator autowire: true
To activate the authenticator go to security.yml and make your firewall to look like that:
firewalls: # disables authentication for assets and the profiler, adapt it according to your needs dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: anonymous: ~ guard: authenticators: - app.security.login_form_authenticator logout: path: /logout
As soon as we do that the function getCredentials will be called on every request on the whole system.
Setting up USER PROVIDER
User provider task in symfony are simple:
- Load user from the session
- Make sure that user is up-to-date
- Handles “remember me” functionality
- Handles “impersonation”
Open security.yml again and make providers to look like that:
providers: our_users: entity: { class: AppBundle\Entity\User, property: email }
The property part is what we will check on login.
Doctrine Listener – Encoding the user provider
Doctrine Listener is called when any entity is inserted or updated.
Create new directory named AppBundle/xDoctrine and new class named HashPasswordListener
passwordEncoder = $passwordEncoder; } public function prePersist(LifecycleEventArgs $args) { $entity = $args->getEntity(); if(!$entity instanceof User){ return; } $this->encodePassword($entity); } public function preUpdate(LifecycleEventArgs $args) { $entity = $args->getEntity(); if(!$entity instanceof User){ return; } $this->encodePassword($entity); //Magic $em = $args->getEntityManager(); $meta = $em->getClassMetadata(get_class($entity)); $em->getUnitOfWork()->recomputeSingleEntityChangeSet($meta, $entity); } public function getSubscribedEvents() { return ['prePersist', 'preUpdate']; } /** * @param User $entity */ private function encodePassword(User $entity) { if (!$entity->getPlainPassword()) { return; } $encoded = $this->passwordEncoder->encodePassword( $entity, $entity->getPlainPassword() ); $entity->setPassword($encoded); } }
To hook it up we need to register it as a service. Open services.yml and add
app.xdoctrine.hash_password_listener: class: AppBundle\xDoctrine\HashPasswordListener autowire: true tags: - { name: doctrine.event_subscriber }
And the last thing we need to do is to set encryption type to our entity. Open security.yml and copy this:
security: encoders: AppBundle\Entity\User: bcrypt