I am implementing a login system with guard in my symfony application.
I already setup the system, but i did one mistake, therefore i cant login...
The essentials:
My User Entity implements "AdvancedUserInterface, \Serializable" and it provides email property instead of username property..moreover i changed "getUsername"-function to:
public function getUsername()
{
return $this->email;
}
security.yml:
# app/config/security.yml
security:
providers:
main_db_provider:
entity:
class: AppBundle:User
property: email
encoders:
AppBundle\Entity\User:
algorithm: bcrypt
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
logout:
path: /logout
target: /
guard:
authenticators:
- form_authenticator
provider: main_db_provider
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_ADMIN }
services.yml:
services:
form_authenticator:
class: AppBundle\Security\FormAuthenticator
arguments: ["#service_container"]
login.html.twig:
<form action="{{ path('login') }}" method="POST">
<input type="text" name="email">
<input type="text" name="password">
<input type="submit" name="submit" value="Submit">
</form>
LoginController(part of it):
/**
* #Route("/login", name="login")
*/
public function loginAction(Request $request) {
$authenticationUtils = $this->get('security.authentication_utils');
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render(
'AppBundle:login:index.html.twig',
[
'error' => $error ? $error->getMessage() : NULL,
'last_username' => $lastUsername
]
);
}
And last my FormAuthentificator:
class FormAuthenticator extends AbstractGuardAuthenticator
{
private $container;
/**
* Default message for authentication failure.
*
* #var string
*/
private $failMessage = 'Invalid credentials';
/**
* Creates a new instance of FormAuthenticator
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* {#inheritdoc}
*/
public function getCredentials(Request $request)
{
if ($request->getPathInfo() != '/login' || !$request->isMethod('POST')) {
return;
}
return array(
'email' => $request->request->get('email'),
'password' => $request->request->get('password'),
);
}
/**
* {#inheritdoc}
*/
public function getUser($credentials, UserProviderInterface $userProvider)
{
$email = $credentials['email'];
return $userProvider->loadUserByUsername($email);
}
/**
* {#inheritdoc}
*/
public function checkCredentials($credentials, UserInterface $user)
{
$plainPassword = $credentials['password'];
$encoder = $this->container->get('security.password_encoder');
if (!$encoder->isPasswordValid($user, $plainPassword)) {
throw new CustomUserMessageAuthenticationException($this->failMessage);
}
return true;
}
/**
* {#inheritdoc}
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
$url = $this->container->get('router')->generate('backend');
return new RedirectResponse($url);
}
/**
* {#inheritdoc}
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
$url = $this->container->get('router')->generate('login');
return new RedirectResponse($url);
}
/**
* {#inheritdoc}
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$url = $this->container->get('router')->generate('login');
return new RedirectResponse($url);
}
/**
* {#inheritdoc}
*/
public function supportsRememberMe()
{
return false;
}
}
When i enter my valid credentials, i get:
Invalid credentials:
I also tried with other credentials, but always same error.
Anybody could help me to solve this issue?
Thanks and Greetings!
Related
Evening everyone , I've been stack with this problem since few days and whatever I change it remains .. basically on login when I put an invalid email it says email not found , when I put invalid password it says wrong password( I edited it of course ) but when they're both correct it shows Invalid credentials for some reason , I m really disparate .. help me please I can link any page code but Im 90% sure the code is correct ,thanks in advance
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 ($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.');
}
}
security.yaml
security:
encoders:
App\Entity\User5:
algorithm: bcrypt
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
app_user_provider:
entity:
class: 'App\Entity\User5'
property: 'email'
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
provider: app_user_provider
form_login:
login_path: app_login
check_path: app_login
guard:
authenticators:
- App\Security\LoginFormAuthenticator
entry_point: App\Security\LoginFormAuthenticator
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
# form_login: true
# https://symfony.com/doc/current/security/form_login_setup.html
# 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 }
# - { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
login.html.twig
{% extends 'home.html.twig' %}
{% block title %}Log in!{% endblock %}
{% block body %}
<form method="post" action="{{ path('app_login')}}">
{% if error %}
<div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}
{% if app.user %}
<div class="mb-3">
You are logged in as {{ app.user.username }}, Logout
</div>
{% endif %}
<h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
<label for="inputEmail">Email</label>
<input type="email" value="{{ last_username }}" name="email" id="inputEmail" class="form-control" required autofocus>
<label for="inputPassword">Password</label>
<input type="password" name="password" id="inputPassword" class="form-control" 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
<?php
namespace App\Security;
use App\Entity\User5;
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;
private 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(User5::class)->findOneBy(['email' => $credentials['email']]);
if (!$user) {
// fail authentication with a custom error
throw new CustomUserMessageAuthenticationException('Email could not be found.');
}
$user = $this->entityManager->getRepository(User5::class)->findOneBy(['password' => $credentials['password']]);
if (!$user) {
// fail authentication with a custom error
throw new CustomUserMessageAuthenticationException('Wrong password.');
}
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'));
return new RedirectResponse($this->urlGenerator->generate('index'));
throw new \Exception('TODO: provide a valid redirect inside '.__FILE__);
}
protected function getLoginUrl()
{
return $this->urlGenerator->generate('app_login');
}
}
User5.php
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity()
* #UniqueEntity("email")
*/
/**
* #ORM\Entity(repositoryClass="App\Repository\User5Repository")
* #UniqueEntity(
* fields={"email"},
* message="That Email is already taken , try another "
* )
*/
class User5 implements UserInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(unique=true, type="string", nullable=false)
*/
private $email;
/**
* #ORM\Column(type="json")
*/
private $roles = [];
/**
* #var string The hashed password
* #ORM\Column(type="string")
*/
private $password;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Role", mappedBy="Users")
*/
private $userRoles;
public function __construct()
{
$this->userRoles = new ArrayCollection();
}
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;
}
/**
* #return Collection|Role[]
*/
public function getUserRoles(): Collection
{
return $this->userRoles;
}
public function addUserRole(Role $userRole): self
{
if (!$this->userRoles->contains($userRole)) {
$this->userRoles[] = $userRole;
$userRole->addUser($this);
}
return $this;
}
public function removeUserRole(Role $userRole): self
{
if ($this->userRoles->contains($userRole)) {
$this->userRoles->removeElement($userRole);
$userRole->removeUser($this);
}
return $this;
}
}
You should remove this lines, the password is encrypted in the database so you can't retrieve a user by his password, Symfony will do this job (comparing the two passwords).
$user = $this->entityManager->getRepository(User5::class)->findOneBy(['password' => $credentials['password']]);
if (!$user) {
// fail authentication with a custom error
throw new CustomUserMessageAuthenticationException('Wrong password.');
}
I solved it my self , apparently you cant use guard and form login in the same time you gotta pick one of them because both do the same job one is easy and one is customized also I forgot something very important in the register function , forgot to encode the password , now it works just fine
heres the new register function
public function register(Request $request , EntityManagerInterface $entityManager, UserPasswordEncoderInterface $encoder)
{
$user = new User5();
$form = $this->createForm(Form::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$hashed = $encoder->encodePassword($user, $user->getPassword());
$user->setPassword($hashed);
$entityManager->persist($user);
$entityManager->flush();
$this->addFlash("success", "Welcome to our application");
return $this->redirectToRoute("app_login");
}
return $this->render('account/registration.html.twig',[
'form' => $form->createView()
]);
}
new security.yaml
security:
encoders:
App\Entity\User5:
algorithm: bcrypt
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
app_user_provider:
entity:
class: 'App\Entity\User5'
property: 'email'
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
provider: app_user_provider
form_login:
login_path: app_login
check_path: app_login
# default_target_path: dashboard
logout:
path: logout_user
target: index
# target: app_any_route
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#firewalls-authentication
# form_login: true
# https://symfony.com/doc/current/security/form_login_setup.html
# 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 }
# - { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
# - { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
I have setup my own login form in a security controller. to use the LoginForm I have configured this in the security configuration.
I want to use a custom login form authenticator to have more control over the authentication progress, register logins in the system,
and do anything I'd like to add (IP-check etc, etc...)
So there is also a LoginFormAuthenticator class in my application. somehow the authentication process doesn't even seem to use the methods of
the custom LoginFormAuthenticator. Is my security.yaml configured properly? how do I get all my configuration to work together?
The security in symfony seems so messy at some points, I can't begin to understand how people manage to properly configure it..
LoginFormAuthenticator:
class LoginFormAuthenticator extends AbstractGuardAuthenticator
{
/**
* Constructor
*
* #param Logger $logger
* #param LoginAttemptManagerInterface $loginAttemptManager
* #param LocationManagerInterface $locationManager
* #param RouterInterface $router
* #param UserPasswordEncoderInterface $userPasswordEncoder
* #param UserRepositoryInterface $userRepository
*/
public function __construct(Logger $logger, LoginAttemptManagerInterface $loginAttemptManager, LocationManagerInterface $locationManager, RouterInterface $router, UserPasswordEncoderInterface $userPasswordEncoder, UserRepositoryInterface $userRepository)
{
$this->_logger = $logger;
$this->_loginAttemptManager = $loginAttemptManager;
$this->_locationManager = $locationManager;
$this->_router = $router;
$this->_userPasswordEncoder = $userPasswordEncoder;
$this->_userRepository = $userRepository;
}
/**
* {#inheritdoc}
*/
protected function getLoginUrl()
{
return $this->_router->generate("login");
}
/**
* {#inheritdoc}
*/
public function getCredentials(Request $request)
{
$credentials = $request->get("login_form");
return [
"username" => $credentials["username"],
"password" => $credentials["password"],
"token" => $credentials["_token"],
];
}
/**
* {#inheritdoc}
*/
public function getUser($credentials, UserProviderInterface $userProvider)
{
$username = $credentials["username"];
try {
$user = $this->_userRepository->findOneByUsername($username);
if (null !== $user && $user instanceof UserInterface) {
/* #var LoginAttempt $loginAttempt */
$loginAttempt = $this->_loginAttemptManager->create();
$user->addLoginAttempt($loginAttempt);
}
}
catch (NoResultException $e) {
return null;
}
catch (NonUniqueResultException $e) {
return null;
}
catch (UsernameNotFoundException $e) {
return null;
}
}
/**
* {#inheritdoc}
*/
public function checkCredentials($credentials, UserInterface $user)
{
/* #var string $rawPassword the unencoded plain password */
$rawPassword = $credentials["password"];
if ($this->_userPasswordEncoder->isPasswordValid($user, $rawPassword)) {
return true;
}
return new CustomUserMessageAuthenticationException("Invalid credentials");
}
/**
* {#inheritdoc}
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
/* #var AbstractUser $user */
$user = $token->getUser();
/* #var LoginAttempt $loginAttempt */
$loginAttempt = $user->getLastLoginAttempt();
$loginAttempt->success();
this->_loginAttemptManager->saveOne($loginAttempt, true);
}
/**
* {#inheritdoc}
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
// without this method the authentication process becomes a loop
}
/**
* {#inheritdoc}
*/
public function start(Request $request, AuthenticationException $authException = null)
{
return new RedirectResponse($this->getLoginUrl());
}
/**
* {#inheritdoc}
*/
public function supports(Request $request)
{
return $request->getPathInfo() != $this->getLoginUrl() || !$request->isMethod(Request::METHOD_POST);
}
/**
* {#inheritdoc}
*/
public function supportsRememberMe()
{
return true;
}
}
SecurityController:
class SecurityController extends AbstractController
{
/**
* #Route(path = "login", name = "login", methods = {"GET", "POST"})
* #Template(template = "security/login.html.twig")
*
* #param AuthenticationUtils $authUtils
* #param Request $request
* #return array
*/
public function login(AuthenticationUtils $authUtils, Request $request)
{
$form = $this->createLoginForm();
if (null !== $authUtils->getLastAuthenticationError()) {
$form->addError(new FormError(
$this->_translator->trans("error.authentication.incorrect-credentials", [], "security")
));
}
if (null != $authUtils->getLastUsername()) {
$form->setData([
"username" => $authUtils->getLastUsername(),
]);
}
// settings are in config/packages/security.yaml
// configuration authenticates user in login form authenticator service
return [
"backgroundImages" => $this->_backgroundImageManager->findAll(),
"form" => $form->createView(),
];
}
/**
* #return FormInterface
*/
private function createLoginForm() : FormInterface
{
$form = $this->createForm(LoginForm::class, null, [
"action" => $this->generateUrl("login"),
"method" => Request::METHOD_POST,
]);
$form->add("submit", SubmitType::class, [
"label" => $this->_translator->trans("btn.login", [], "button"),
"icon_name" => "sign-in",
"translation_domain" => false,
]);
return $form;
}
}
security.yaml:
security:
providers:
user_provider:
entity:
class: App\Entity\Model\AbstractUser
property: username
oauth_provider:
entity:
class: App\Entity\Model\ApiClient
property: name
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
# The API-Oauth-Token-Firewall must be above the API-firewall
api_oauth_token:
pattern: ^/api/oauth/token$
security: false
# The API-firewall must be above the Main-firewall
api:
pattern: ^/api/*
security: true
stateless: true
oauth2: true
provider: oauth_provider
access_denied_handler: App\Service\Api\Security\ApiAccessDeniedHandler
main:
anonymous: true
guard:
authenticators:
- App\Service\Security\LoginFormAuthenticator
access_denied_handler: App\Service\Security\AccessDeniedHandler
provider: user_provider
form_login:
login_path: /login
check_path: /login
default_target_path: / #index
username_parameter: "login_form[username]"
password_parameter: "login_form[password]"
logout:
# the logout path overrides the implementation of the logout method
# in the security controller
path: /logout
target: / #index
remember_me:
secret: '%kernel.secret%'
lifetime: 43200 # 60 sec * 60 min * 12 hours
path: /
remember_me_parameter: "login_form[remember]"
encoders:
App\Entity\Model\AbstractUser:
algorithm: bcrypt
cost: 13
access_control:
# omitted from this question
role_hierarchy:
# omitted from this question
How did you come up with the logic of LoginFormAuthenticator::supports()?
shouldn't this be the opposite like:
return 'login' === $request->attributes->get('_route')
&& $request->isMethod('POST');
Apparently I had two forms of authentication configured in security.yaml
So I've removed the form_login key from the config:
security.yaml
security:
providers:
user_provider:
entity:
class: App\Entity\Model\AbstractUser
property: username
oauth_provider:
entity:
class: App\Entity\Model\ApiClient
property: name
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
# The API-Oauth-Token-Firewall must be above the API-firewall
api_oauth_token:
pattern: ^/api/oauth/token$
security: false
# The API-firewall must be above the Main-firewall
api:
pattern: ^/api/*
security: true
stateless: true
oauth2: true
provider: oauth_provider
access_denied_handler: App\Service\Api\Security\ApiAccessDeniedHandler
main:
anonymous: true
guard:
authenticators:
- App\Service\Security\LoginFormAuthenticator
access_denied_handler: App\Service\Security\AccessDeniedHandler
provider: user_provider
logout:
# the logout path overrides the implementation of the logout method
# in the security controller
path: /logout
target: / #index
remember_me:
secret: '%kernel.secret%'
lifetime: 43200 # 60 sec * 60 min * 12 hours
path: /
remember_me_parameter: "login_form[remember]"
And updated the LoginFormAuthenticator
- integrated
- also added checking CSRF token
LoginFormAuthenticator
class LoginFormAuthenticator extends AbstractGuardAuthenticator
{
const FORM = "login_form";
const USERNAME = "username";
const PASSWORD = "password";
const CSRF_TOKEN = "token";
/**
* Constructor
*
* #param CsrfTokenManagerInterface $csrfTokenManager
* #param Logger $logger
* #param LoginAttemptManagerInterface $loginAttemptManager
* #param LocationManagerInterface $locationManager
* #param RouterInterface $router
* #param UserPasswordEncoderInterface $userPasswordEncoder
* #param UserRepositoryInterface $userRepository
*/
public function __construct(CsrfTokenManagerInterface $csrfTokenManager, Logger $logger, LoginAttemptManagerInterface $loginAttemptManager, LocationManagerInterface $locationManager, RouterInterface $router, UserPasswordEncoderInterface $userPasswordEncoder, UserRepositoryInterface $userRepository)
{
$this->_csrfTokenManager = $csrfTokenManager;
$this->_logger = $logger;
$this->_loginAttemptManager = $loginAttemptManager;
$this->_locationManager = $locationManager;
$this->_router = $router;
$this->_userPasswordEncoder = $userPasswordEncoder;
$this->_userRepository = $userRepository;
}
/**
* Get Login URL
*
* #return string
*/
protected function getLoginUrl()
{
return $this->_router->generate("login");
}
/**
* Get Target URL
*
* #return string
*/
protected function getTargetUrl()
{
return $this->_router->generate("index");
}
/**
* {#inheritdoc}
*/
public function getCredentials(Request $request)
{
$credentials = $request->request->get(self::FORM);
$request->getSession()->set(Security::LAST_USERNAME, $credentials["username"]);
return [
self::USERNAME => $credentials["username"],
self::PASSWORD => $credentials["password"],
self::CSRF_TOKEN => $credentials["_token"],
];
}
/**
* {#inheritdoc}
*/
public function getUser($credentials, UserProviderInterface $userProvider)
{
$username = $credentials[self::USERNAME];
try {
$user = $this->_userRepository->findOneByUsername($username);
if (null !== $user && $user instanceof UserInterface) {
/* #var LoginAttempt $loginAttempt */
$loginAttempt = $this->_loginAttemptManager->create();
$user->addLoginAttempt($loginAttempt);
}
return $user;
}
catch (NoResultException $e) {
throw new BadCredentialsException("Authentication failed");
}
catch (NonUniqueResultException $e) {
throw new BadCredentialsException("Authentication failed");
}
}
/**
* {#inheritdoc}
*/
public function checkCredentials($credentials, UserInterface $user)
{
$csrfToken = new CsrfToken(self::FORM, $credentials[self::CSRF_TOKEN]);
if (false === $this->_csrfTokenManager->isTokenValid($csrfToken)) {
throw new InvalidCsrfTokenException('Invalid CSRF token');
}
/* #var string $rawPassword the unencoded plain password */
$rawPassword = $credentials[self::PASSWORD];
if ($this->_userPasswordEncoder->isPasswordValid($user, $rawPassword)) {
return true;
}
/* #var AbstractUser $user */
$loginAttempt = $user->getLastLoginAttempt();
if (null !== $loginAttempt) {
$this->_loginAttemptManager->saveOne($loginAttempt);
}
return new CustomUserMessageAuthenticationException("Invalid credentials");
}
/**
* {#inheritdoc}
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
/* #var AbstractUser $user */
$user = $token->getUser();
/* #var LoginAttempt $loginAttempt */
$loginAttempt = $user->getLastLoginAttempt();
$loginAttempt->setStatus(LoginAttempt::STATUS_AUTHENTICATION_SUCCESS);
if (null !== $loginAttempt) {
$this->_loginAttemptManager->saveOne($loginAttempt);
}
return new RedirectResponse($this->getTargetUrl());
}
/**
* {#inheritdoc}
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception->getMessage());
return new RedirectResponse($this->getLoginUrl());
}
/**
* {#inheritdoc}
*/
public function start(Request $request, AuthenticationException $authException = null)
{
return new RedirectResponse($this->getLoginUrl());
}
/**
* {#inheritdoc}
*/
public function supports(Request $request)
{
return $request->getPathInfo() === $this->getLoginUrl() && $request->isMethod(Request::METHOD_POST);
}
/**
* {#inheritdoc}
*/
public function supportsRememberMe()
{
return true;
}
}
I'm implementing a login system with guard authentication in my symfony application. I've already started to implement the system but I must be doing something incorrectly.
I will start by showing what I have implemented and in the end explain what is happening ...
security.yml
security:
encoders:
UserBundle\Entity\User: bcrypt
providers:
custom_own_provider:
entity:
class: UserBundle:User
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
custom:
pattern: ^/ad/
anonymous: ~
provider: custom_own_provider
remember_me:
name: 'nothing'
secure: true
httponly: true
secret: '%secret%'
lifetime: 604800 # 1 week in seconds
path: /
domain: ~
guard:
authenticators:
- app.authenticator.form
services.yml
services:
app.authenticator.form:
class: UserBundle\Security\LoginFormAuthenticator
autowire: true
arguments: ["#service_container"]
LoginController
/**
* #Route("/login", name="login")
*/
public function loginAction(Request $request) {
$authenticationUtils = $this->get('security.authentication_utils');
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render(
'AppBundle:login:index.html.twig',
[
'error' => $error ? $error->getMessage() : NULL,
'last_username' => $lastUsername
]
);
}
Public controller with homepage: After login success, the user is redirected here. It happens that here, when validated if the user is authenticated I am unsuccessful. the 'getuser' from the security token is returning 'anon'.
/**
* #Route("/", name="homepage")
*/
public function publicHomepageAction (){
// DEBUG: This method gets a user from the Security Token Storage. The user here comes as 'anon'.
$user = $this->getUser();
// If user is already logged show internal homepage
$securityContext = $this->container->get('security.authorization_checker');
if($securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED')
|| $securityContext->isGranted('IS_AUTHENTICATED_FULLY')
){
// Code if user is authenticated
...
}
return $this->render('splash_page/homepage.html.twig');
}
And finally, my FormAuthenticator:
class LoginFormAuthenticator extends AbstractGuardAuthenticator
{
private $container;
/**
* Default message for authentication failure.
*
* #var string
*/
private $failMessage = 'Invalid credentials';
/**
* Creates a new instance of FormAuthenticator
*/
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* {#inheritdoc}
*/
public function getCredentials(Request $request)
{
if ($request->getPathInfo() != '/login' || !$request->isMethod('POST')) {
return;
}
return array(
'email' => $request->request->get('email'),
'password' => $request->request->get('password'),
);
}
/**
* {#inheritdoc}
*/
public function getUser($credentials, UserProviderInterface $userProvider)
{
$email = $credentials['email'];
return $userProvider->loadUserByUsername($email);
}
/**
* {#inheritdoc}
*/
public function checkCredentials($credentials, UserInterface $user)
{
$plainPassword = $credentials['password'];
$encoder = $this->container->get('security.password_encoder');
if (!$encoder->isPasswordValid($user, $plainPassword)) {
throw new CustomUserMessageAuthenticationException($this->failMessage);
}
return true;
}
/**
* {#inheritdoc}
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
$url = $this->container->get('router')->generate('homepage');
return new RedirectResponse($url);
}
/**
* {#inheritdoc}
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
$url = $this->container->get('router')->generate('login');
return new RedirectResponse($url);
}
/**
* {#inheritdoc}
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$url = $this->container->get('router')->generate('login');
return new RedirectResponse($url);
}
/**
* {#inheritdoc}
*/
public function supportsRememberMe()
{
return true;
}
/**
* Does the authenticator support the given Request?
*
* If this returns false, the authenticator will be skipped.
*
* #param Request $request
*
* #return bool
*/
public function supports (Request $request){
return $request->request->has('_username') && $request->request->has('_password');
}
}
My User Entity implements UserInterface.
Although on the homepage it indicates to me that I am authenticated as 'anon', if I try to login again, through the debug, I noticed that it detects that I am authenticated and redirects to the homepage controller but in this controller it says that I am like anon and back to redirect to the splash page.
It gives the feeling that somewhere between validation and redirection to the controller loses token storage.
Any idea?
I try to use Guard to make a login form instead of the security.yml way.
Getuser and checkcredential are ok.
onAuthenticationSuccess is ok (if I put a dump($token); die; in onAuthenticationSuccess I can see my user in the token) and redirect to /accueil.
But when it arrived on /accueil it's send back to /login because user authentication is always on anon !
Impossible to find a solution :c/
Firewall in security.yml :
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login_firewall:
pattern: ^/login$
anonymous: true
main:
pattern: ^/
anonymous: ~
logout: ~
switch_user: true
guard:
provider: database
authenticators:
- ent.login_authenticator
access_control:
- { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/, roles: ROLE_ADMIN }
- { path: ^/, roles: ROLE_USER }
securityController
/**
* #Route("/login", name="login")
*
*/
public function loginAction(Request $request)
{
if ($this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
return $this->redirectToRoute('accueil');
}
$authenticationUtils = $this->get('security.authentication_utils');
$exception = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('EntBundle::login.html.twig', [
'last_username' => $lastUsername,
'error' => $exception,
]);
}
/**
* #Route("/login_check", name="login_check")
*/
public function loginCheckAction()
{
// this controller will not be executed,
// as the route is handled by the Security system
}
loginAuthenticator:
public function __construct(RouterInterface $router, UserPasswordEncoder $passwordEncoder, EntityManager $em) {
$this->router = $router;
$this->passwordEncoder = $passwordEncoder;
$this->em = $em;
}
public function getCredentials(Request $request)
{
if ($request->getPathInfo() != '/login_check' ) {
return null;
}
$request->getSession()->set(Security::LAST_USERNAME, $request->request->get('_username'));
return array(
'username' => $request->request->get('_username'),
'password' => $request->request->get('_password'),
);
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
try {
return $this->em->getRepository('EntBundle:User\User')->findOneBy(array('username' => $credentials ));
}
catch (UsernameNotFoundException $e) {
throw new CustomUserMessageAuthenticationException($this->failMessage);
}
}
public function checkCredentials($credentials, UserInterface $user) {
$plainPassword = $credentials['password'];
if ($this->passwordEncoder->isPasswordValid($user, $plainPassword)) {
return true;
}
throw new CustomUserMessageAuthenticationException($this->failMessage);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// dump($token); die;
$url = $this->router->generate('accueil');
return new RedirectResponse($url);
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);
$url = $this->router->generate('login');
return new RedirectResponse($url);
}
public function start(Request $request, AuthenticationException $authException = null)
{
$url = $this->router->generate('login');
return new RedirectResponse($url);
}
sorry for the late response, the piece of code would look something like this to set the token in a symfony3 application:
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
and the actual setting of the token part will be like:
$token = new UsernamePasswordToken($user, $user->getPassword(), "firewall goes here for example: main", $user->getRoles());
$this->get("security.token_storage")->setToken($token);
i hope i helped you with this :)
I have 4 different user types in a system (on top of Symfony 2). Each type have some specific fields and behaviour, but all of them have a common base. So it seems it would be good idea to implement single class for each user extending the same superclass.
How can it be achieved? All I have found on the topic is some RollerworksMultiUserBundle.
Using table inheritance in the ORM level and OOP inheritance. Go for Single Table Inheritance if performance is critical (no JOINs) or Class Table Inheritance if you are a purist.
E.g.
Common base class:
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
/**
* #ORM\Entity(repositoryClass="Some\Bundle\Repository\UserRepository")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="userType", type="string")
* #ORM\DiscriminatorMap({
* "userType1" = "UserType1",
* "userType2" = "UserType2",
* "userType3" = "UserType3",
* "userType4" = "UserType4"
* })
*/
abstract class User implements AdvancedUserInterface
{
/**
* #ORM\Id()
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=250, unique=true)
*/
protected $email;
/**
* #ORM\Column(type="string", length=128, nullable=true)
*/
protected $password;
// other fields
public function getSalt()
{
return "some salt number";
}
public function getUsername()
{
return $this->email;
}
public function getPassword()
{
return $this->password;
}
public function getRoles()
{
return array('ROLE_USER');
}
public function eraseCredentials() {}
public function isCredentialsNonExpired()
{
return true;
}
public function isAccountNonLocked()
{
return true;
}
public function isAccountNonExpired()
{
return true;
}
public function isEnabled()
{
return true;
}
public function equals(UserInterface $user)
{
return $user->getUsername() === $this->getUsername() || $user->getEmail() === $this->getEmail();
}
}
The children classes are straightforward (below an example for class UserType1 only):
/**
* #ORM\Entity
*/
class UserType1 extends User
{
// fields of UserType1 class go here
public function getRoles()
{
return array('ROLE_USER_TYPE_1', 'ROLE_USER');
}
}
The rest is pretty much like in the examples. In security.yml:
security:
encoders:
Some\Bundle\Repository\User:
algorithm: sha512
encode_as_base64: false
iterations: 1000
providers:
provider1:
entity: { class: "SomeBundle:User" }
role_hierarchy:
ROLE_USER_TYPE_1: ROLE_USER
ROLE_USER_TYPE_2: ROLE_USER
ROLE_USER_TYPE_3: ROLE_USER
ROLE_USER_TYPE_4: ROLE_USER
firewalls:
firewall1:
pattern: ^/
provider: provider1
form_login:
login_path: /login
check_path: /auth
post_only: true
username_parameter: email
password_parameter: password
always_use_default_target_path: true
default_target_path: /
logout:
path: /logout
target: /login
anonymous: ~
The repository class:
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Security\Core\User\UserInterface;
class UserRepository extends EntityRepository implements UserProviderInterface
{
public function loadUserByUsername($username)
{
$qb = $this->createQueryBuilder('u');
$query = $qb->where('LOWER(u.email) = :email')
->setParameter('email', strtolower($username))
->getQuery();
try {
$user = $query->getSingleResult();
}
catch (NoResultException $e) {
throw new UsernameNotFoundException('User not found.', null, $e);
}
return $user;
}
public function supportsClass($class)
{
return $this->getEntityName() === $class ||
is_subclass_of($class, $this->getEntityName());
}
public function refreshUser(UserInterface $user)
{
$class = get_class($user);
if (!$this->supportsClass($class)) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $class));
}
return $this->find($user->getId());
}
}