Issue with log in in symfony 6 - php

I have next code:
security.yaml
security:
enable_authenticator_manager: true
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
providers:
app_user_provider:
entity:
class: App\Entity\User
property: login
firewalls:
login:
pattern: ~/api/user/login
stateless: true
json_login:
check_path: api_login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
api:
pattern: ~/api
stateless: true
jwt: ~
access_control:
- { path: ~/api/user, roles: PUBLIC_ACCESS }
- { path: ~/api, roles: IS_AUTHENTICATED_FULLY }
UserController.php
...
/**
* #Route("/api/user")
* #OA\Tag(name="[User] User Controller")
*/
class UserController extends AbstractController
{
...
#[Route('/login', name: 'api_login_check', methods: 'POST')]
public function getTokenUser(#[CurrentUser] ?User $user, JWTTokenManagerInterface $JWTManager): JsonResponse
{
if (null === $user) {
return $this->json([
'message' => 'missing credentials',
], Response::HTTP_UNAUTHORIZED);
}
return $this->json([
'user' => $user->getUserIdentifier(),
'token' => $JWTManager->create($user),
]);
}
}
User.php
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: '`user`')]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private $id;
#[ORM\Column(type: 'string', length: 64)]
private $login;
#[ORM\Column(type: 'string', length: 255)]
private $password;
#[ORM\Column(type: 'boolean')]
private $teacher;
#[ORM\Column(type: 'string', length: 128, nullable: true)]
private $lessonStudy;
#[ORM\Column(type: 'integer', nullable: true)]
private $classNumber;
#[ORM\Column(type: 'string', length: 8, nullable: true)]
private $classChar;
#[ORM\Column(type: 'datetime')]
private $created_at;
public function __construct()
{
$this->created_at = new \DateTime();
$this->teacher = false;
}
public function getId(): ?int
{
return $this->id;
}
public function getLogin(): ?string
{
return $this->login;
}
public function setLogin(string $login): self
{
$this->login = $login;
return $this;
}
/**
* #see PasswordAuthenticatedUserInterface
*/
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
public function getTeacher(): ?bool
{
return $this->teacher;
}
public function setTeacher(bool $teacher): self
{
$this->teacher = $teacher;
return $this;
}
public function getLessonStudy(): ?string
{
return $this->lessonStudy;
}
public function setLessonStudy(?string $lessonStudy): self
{
$this->lessonStudy = $lessonStudy;
return $this;
}
public function getClassNumber(): ?int
{
return $this->classNumber;
}
public function setClassNumber(?int $classNumber): self
{
$this->classNumber = $classNumber;
return $this;
}
public function getClassChar(): ?string
{
return $this->classChar;
}
public function setClassChar(?string $classChar): self
{
$this->classChar = $classChar;
return $this;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->created_at;
}
public function setCreatedAt(\DateTimeInterface $created_at): self
{
$this->created_at = $created_at;
return $this;
}
/**
* #return array
*/
public function getRoles(): array
{
return array('ROLE_USER');
}
/**
* #see UserInterface
*/
public function eraseCredentials()
{
}
/**
* #return string
*/
public function getUserIdentifier(): string
{
return (string) $this->login;
}
/**
* 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;
}
}
When I try make request to /api/user/login -> get error from controller about bad creds.
curl --location --request POST 'http://127.0.0.1:8000/api/user/login' \
--header 'Content-Type: application/json' \
--data-raw '{
"username": "cruiservlad",
"password": "pro100cruiser"
}'
What I did wrong?

Related

Symfony security uses the wrong sql table alias during authentication

I am trying to make my own User entity which extends the SuluUser entity (so I can add some properties). After following the instructions from the Sulu documentation: Extend Entities I then created my own symfony authentication for the (front) website however, when I try to authenticate I get the following error.
Edit: added the imports and annotations for the user entity
An exception occurred while executing 'SELECT t1.username AS username_2, t1.password AS password_3, t1.locale AS locale_4, t1.salt AS salt_5, t1.locked AS locked_6, t1.enabled AS enabled_7, t1.lastLogin AS lastLogin_8, t1.confirmationKey AS confirmationKey_9, t1.passwordResetToken AS passwordResetToken_10, t1.passwordResetTokenExpiresAt AS passwordResetTokenExpiresAt_11, t1.passwordResetTokenEmailsSent AS passwordResetTokenEmailsSent_12, t1.privateKey AS privateKey_13, t1.apiKey AS apiKey_14, t1.email AS email_15, t1.id AS id_16, t1.firstname AS firstname_17, t1.lastname AS lastname_18, t1.phonenumber AS phonenumber_19, t1.gender AS gender_20, t1.password_changed_date AS password_changed_date_21, t1.confirmation_token AS confirmation_token_22, t1.idContacts AS idContacts_23 FROM user t1 WHERE t0.email = ? LIMIT 1' with params ["test#test.com"]:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 't0.email' in 'where clause'
I'm not sure why it's using t0.email when the rest of the query uses t1 as an alias, but this breaks the login from the (front) website. Admin can login just fine for the sulu backend. I believe it has something to do with the inheritance with the SuluUser that my User entity extends. Any help would be very much appreciated. I have read about doctrine inheritance here but I don't think that applies to me as I cannot (should not) change the class in Sulu\Bundle\SecurityBundle\Entity\User. I have configured the following
App\Entity\User
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Sulu\Bundle\SecurityBundle\Entity\User as SuluUser;
use Symfony\Component\Security\Core\Validator\Constraints\UserPassword;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #UniqueEntity(fields={"email"})
*/
class User extends SuluUser
{
/**
* #Groups({"get", "post", "put", "get-comment-with-author", "get-blog-post-with-author"})
* #ORM\Column(type="string", length=25)
* #Assert\NotBlank(groups={"post"})
* #Assert\Length(min=4, max="100")
*/
private $Firstname;
/**
* #Groups({"get", "post", "put"})
* #ORM\Column(type="string", length=25)
* #Assert\NotBlank(groups={"post"})
* #Assert\Length(min=4, max="100")
*/
private $Lastname;
/**
* #ORM\Column(type="string", length=10, nullable=true)
*
* #Groups({"get", "post", "put"})
*/
private $phonenumber;
/**
* #ORM\Column(type="string", nullable=true)
* #Groups({"get", "post", "put"})
* #Assert\Collection()
*/
private $gender;
/**
* #Groups({"post"})
* #Assert\NotBlank(groups={"post"})
* #Assert\Expression(
* "this.getPassword() === this.getRetypedPassword()",
* message="Passwords do not match"
* )
*/
private $retypedPassword;
/**
* #Assert\Length(min=10, max="100")
* #Assert\NotBlank(groups={"put-reset-password"})
* #Groups({"put-reset-password"})
* #Assert\Regex(
* pattern="/(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9]).{7,}/",
* message="Your password needs to be at least 10 characters long and contain the folloiwing"
* )
*/
private $newPassword;
/**
* #Groups({"put-reset-password"})
* #Assert\NotBlank(groups={"put-reset-password"})
* #Assert\Expression(
* "this.getNewPassword() === this.getNewRetypedPassword()",
* message="Passwords does not match",
* groups={"put-reset-password"}
* )
*/
private $newRetypedPassword;
/**
* #Groups({"put-reset-password"})
* #Assert\NotBlank(groups={"put-reset-password"})
* #UserPassword(groups={"put-reset-password"})
*/
private $oldPassword;
/**
* #ORM\Column(type="integer", nullable=true)
*/
private $passwordChangedDate;
/**
* #ORM\Column(type="string", length=40, nullable=true)
*/
private $confirmationToken;
public function __construct()
{
$this->confirmationToken = null;
}
public function getFirstname(): ?string
{
return $this->Firstname;
}
public function setFirstname( $Firstname): self
{
$this->Firstname = $Firstname;
return $this;
}
public function getLastname(): ?string
{
return $this->Lastname;
}
public function setLastname( $Lastname): self
{
$this->Lastname = $Lastname;
return $this;
}
public function getPhonenumber(): ?string
{
return $this->phonenumber;
}
public function setPhonenumber(?string $phonenumber): self
{
$this->phonenumber = $phonenumber;
return $this;
}
/**
* #return mixed
*/
public function getGender()
{
return $this->gender;
}
/**
* #param mixed $gender
*/
public function setGender($gender): void
{
$this->gender = $gender;
}
/**
* #return mixed
*/
public function getRetypedPassword()
{
return $this->retypedPassword;
}
/**
* #param mixed $retypedPassword
*/
public function setRetypedPassword($retypedPassword): void
{
$this->retypedPassword = $retypedPassword;
}
public function getNewPassword(): ?string
{
return $this->newPassword;
}
public function setNewPassword($newPassword): void
{
$this->newPassword = $newPassword;
}
public function getNewRetypedPassword(): ?string
{
return $this->newRetypedPassword;
}
public function setNewRetypedPassword($newRetypedPassword): void
{
$this->newRetypedPassword = $newRetypedPassword;
}
public function getOldPassword(): ?string
{
return $this->oldPassword;
}
public function setOldPassword($oldPassword): void
{
$this->oldPassword = $oldPassword;
}
public function getPasswordChangedDate()
{
return $this->passwordChangedDate;
}
public function setPasswordChangedDate($passwordChangedDate): void
{
$this->passwordChangedDate = $passwordChangedDate;
}
public function getConfirmationToken()
{
return $this->confirmationToken;
}
public function setConfirmationToken($confirmationToken): void
{
$this->confirmationToken = $confirmationToken;
}
public function __toString(): string
{
return $this->Firstname . ' ' . $this->Lastname;
}
}
App\config\packges\security_website.yaml
security:
encoders:
App\Entity\User:
algorithm: auto
providers:
app_user_provider:
entity:
class: App\Entity\User
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: false
lazy: true
provider: app_user_provider
guard:
authenticators:
- App\Security\AppAuthenticator
logout:
path: app_logout
# where to redirect after logout
target: home
App\Security\AppAuthenticator
class AppAuthenticator 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(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)
{
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);
}
$role = $token->getUser()->getRoles();
// For example : return new RedirectResponse($this->urlGenerator->generate('some_route'));
// throw new \Exception('TODO: provide a valid redirect inside '.__FILE__);
// return new RedirectResponse('admin');
return new RedirectResponse('/');
}
protected function getLoginUrl()
{
return $this->urlGenerator->generate(self::LOGIN_ROUTE);
}
}
Its very important when you override the Sulu entities that you set it on the same table name else the override of the entity does not work correctly so as in the documentation set the #ORM\Table with "se_users" and #ORM\Entity to it.
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Sulu\Bundle\SecurityBundle\Entity\User as SuluUser;
/**
* Following annotations are required and should not be changed:
*
* #ORM\Table(name="se_users")
* #ORM\Entity
*/
class User extends SuluUser
{
}

Symfony 5 : Users still anonymous after login (sometimes)

I'm using a custom authenticator and a custom user provider in Symfony 5.0.10
Some of my users have complained that they can't login anymore : in fact in some cases, the login will be sucessful (onAuthenticationSuccess is called) but the user will still be anonymous. This causes a direct redirection to login page.
This is solved by clearing the cookies (PHPSESSID) or by using a private navigation window. I can't explain how thats comes into the login logic of an anonymous user.
If you guys can find the issue that would really help me, i've been spending a few all nighters on this and can't figure it out.
Here is my code :
security.yaml
security:
encoders:
App\Security\User:
algorithm: none
# 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_galette_user_provider:
id: App\Security\GaletteUserProvider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: lazy
provider: app_galette_user_provider
logout:
path: app_logout
guard:
authenticators:
- App\Security\AppCustomAuthenticator
# 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: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: IS_AUTHENTICATED_FULLY }
Custom Authenticator (AppCustomAuthenticator.php)
<?php
namespace App\Security;
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\Exception\UsernameNotFoundException;
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 AppCustomAuthenticator extends AbstractFormLoginAuthenticator implements PasswordAuthenticatedInterface
{
private $urlGenerator;
private $csrfTokenManager;
private $passwordEncoder;
public function __construct(UrlGeneratorInterface $urlGenerator, UserPasswordEncoderInterface $passwordEncoder)
{
$this->urlGenerator = $urlGenerator;
$this->passwordEncoder = $passwordEncoder;
}
public function supports(Request $request)
{
return 'app_login' === $request->attributes->get('_route') && $request->isMethod('POST');
}
public function getCredentials(Request $request)
{
$credentials = [
'username' => $request->request->get('email'),
'password' => $request->request->get('password'),
//'csrf_token' => $request->request->get('_csrf_token'),
];
$request->getSession()->set(
Security::LAST_USERNAME,
$credentials['username']
);
return $credentials;
}
public function supportsRememberMe()
{
return false;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
// Load / create our user however you need.
// You can do this by calling the user provider, or with custom logic here.
try {
$user = $userProvider->loadUserByUsername($credentials['username']);
} catch (UsernameNotFoundException $e) {
throw new CustomUserMessageAuthenticationException("Erreur lors de la connexion : veuillez vérifier vos identifiants et l'état de votre cotisation.");
}
if (!$user) {
// fail authentication with a custom error
throw new CustomUserMessageAuthenticationException('Email could not be found.');
}
return $user;
}
public function checkCredentials($credentials, UserInterface $user)
{
if ($credentials['password'] === $user->getPassword()) {
return true;
}
return false;
}
/**
* 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)
{
return new RedirectResponse($this->urlGenerator->generate('index'));
}
protected function getLoginUrl()
{
return $this->urlGenerator->generate("app_login");
}
}
And my User.php entity :
<?php
namespace App\Security;
use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Security\Core\User\UserInterface;
class User implements UserInterface, EquatableInterface
{
private $id;
private $email;
private $roles;
private $password;
private $nom;
private $prenom;
private $adresse;
private $adresse2;
private $cp;
private $ville;
private $pays;
private $tel;
private $gsm;
private $salt;
private $username;
public function isEqualTo(UserInterface $user)
{
if ($this->getUsername() !== $user->getUsername()) {
return false;
}
return true;
}
/**
* #return int
*/
public function getId(): int
{
return $this->id;
}
/**
* #param int $id
*/
public function setId($id): void
{
$this->id = $id;
}
/**
* #return string
*/
public function getNom(): string
{
return $this->nom;
}
/**
* #param string $nom
*/
public function setNom($nom): void
{
$this->nom = $nom;
}
/**
* #return string
*/
public function getPrenom(): string
{
return $this->prenom;
}
/**
* #param string $prenom
*/
public function setPrenom($prenom): void
{
$this->prenom = $prenom;
}
/**
* #return string
*/
public function getAdresse(): string
{
return $this->adresse;
}
/**
* #param string $adresse
*/
public function setAdresse($adresse): void
{
$this->adresse = $adresse;
}
/**
* #return string
*/
public function getAdresse2(): ?string
{
return $this->adresse2;
}
/**
* #param string $adresse2
*/
public function setAdresse2($adresse2): void
{
$this->adresse2 = $adresse2;
}
/**
* #return string
*/
public function getCp(): ?string
{
return $this->cp;
}
/**
* #param string $cp
*/
public function setCp($cp): void
{
$this->cp = $cp;
}
/**
* #return string
*/
public function getVille(): ?string
{
return $this->ville;
}
/**
* #param string $ville
*/
public function setVille($ville): void
{
$this->ville = $ville;
}
/**
* #return string
*/
public function getPays(): ?string
{
return $this->pays;
}
/**
* #param string $pays
*/
public function setPays($pays): void
{
$this->pays = $pays;
}
/**
* #return string
*/
public function getTel(): ?string
{
return $this->tel;
}
/**
* #param string $tel
*/
public function setTel($tel): void
{
$this->tel = $tel;
}
/**
* #return string
*/
public function getGsm(): ?string
{
return $this->gsm;
}
/**
* #param string $gsm
*/
public function setGsm($gsm): void
{
$this->gsm = $gsm;
}
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 $this->username;
}
public function setUsername(string $username): self
{
$this->username = $username;
return $this;
}
/**
* #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()
{
return;
}
/**
* #see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
}
'''
-> I'm not including my custom user provider as i know it to work (it correctly returns the user)
-> My user passwords are indeed "in clear", this is a very specific scenario which poses no security threat
This may be caused by symfony session fixation protection.
It is enabled by default and should refresh session id after user authentication. More info in symfony docs
Check if the PHPSESSID cookie refreshes after EVERY request.
If it does, then your authenticator triggers this method refreshing session id on each user request.
Which leads to the following: if the user makes second request before they receive the response from the previous, their session id becomes invalid, and they become unauthenticated.
You can of course disable this protection in your security config:
security:
session_fixation_strategy: none
but better is to fix the problem and do not create a vulnerability in your system.

Symfony __PHP_Incomplete_Class could not be converted to string

When I submit my login form I get that error. I read here that this could be caused by trying to
deserialze object without loaded class for that object
however because the code is hidden away within symfony internals I can't work out why a class wouldn't be loaded in that instance. Profiler seems to say that no queries have been performed, yet if I put in the wrong credentials it says "invalid credentials" so it must be accessing the DB correctly.
User class
<?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("email", message="This email is already in use.")
* #UniqueEntity("username", message="This username is already in use")
*/
class User implements UserInterface, \Serializable
{
private $roles = "ROLE_USER";
/**
* #ORM\Column(name="salt",type="string", length=255)
*/
private $salt = "saltyboye";
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(name="username",type="string", length=255, unique=true)
*/
private $username;
/**
* #ORM\Column(name = "password", type="string", length=255)
*/
private $password;
/**
* #ORM\Column(name="email", type="string", length=255, unique=true)
*/
private $email;
/**
* #ORM\Column(type="datetime")
*/
private $registeredOn;
/**
* #ORM\Column(type="integer", nullable=true)
*/
private $referrer;
/**
* #ORM\Column(type="smallint")
*/
private $entries;
/**
* #ORM\Column(type="string", length=3)
*/
private $currency;
/** #see \Serializable::serialize() */
public function serialize()
{
return serialize(array(
$this->registeredOn,
$this->id,
$this->email,
$this->username,
$this->password,
$this->roles,
$this->referrer,
$this->currency,
$this->entries,
$this->salt));
}
public function unserialize($serialized)
{
list (
$this->id,
$this->email,
$this->username,
$this->password,
$this->roles,
$this->referrer,
$this->currency,
$this->entries,
$this->salt) = unserialize($serialized, array('allowed_classes' => false));
}
public function eraseCredentials()
{
}
public function getRoles()
{
return array("ROLE_USER");
}
public function getSalt()
{
return $this->salt;
}
public function getId()
{
return $this->id;
}
public function getUsername(): ?string
{
return $this->username;
}
public function setUsername(string $username): self
{
$this->username = $username;
return $this;
}
public function getPassword(): ?string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
public function getRegisteredOn(): ?\DateTimeInterface
{
return $this->registeredOn;
}
public function setRegisteredOn(\DateTimeInterface $registeredOn): self
{
$this->registeredOn = $registeredOn;
return $this;
}
public function getReferrer(): ?int
{
return $this->referrer;
}
public function setReferrer(?int $referrer): self
{
$this->referrer = $referrer;
return $this;
}
public function getEntries(): ?smallint
{
return $this->entries;
}
public function setEntries($entries): self
{
$this->entries = $entries;
return $this;
}
public function setCurrency($currency): self
{
$this->currency = $currency;
return $this;
}
public function getCurrency(): ?string
{
return $this->currency;
}
}
Security.yaml
security:
hide_user_not_found: false
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
encoders:
App\Entity\User: sha256
providers:
in_memory: { memory: ~ }
main_db_provider:
entity:
class: App\Entity\User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
# anonymous: true
pattern: ^/ #test
anonymous: ~
form_login:
login_path: login
check_path: login
csrf_token_generator: security.csrf.token_manager
provider: main_db_provider
# activate different ways to authenticate
# http_basic: true
# https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate
# form_login: true
# https://symfony.com/doc/current/security/form_login_setup.html
# 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: ^/$, roles: ROLE_USER }
# - { path: ^/$, roles: ROLE_USER }
The serialize function doesnot match the unserialize function! You need to change it:
/** #see \Serializable::serialize() */
public function serialize()
{
return serialize(array(
$this->registeredOn,
$this->id,
$this->email,
$this->username,
$this->password,
$this->roles,
$this->referrer,
$this->currency,
$this->entries,
$this->salt));
}
public function unserialize($serialized)
{
list (
$this->registeredOn, # <--- look here it is missing
$this->id,
$this->email,
$this->username,
$this->password,
$this->roles,
$this->referrer,
$this->currency,
$this->entries,
$this->salt) = unserialize($serialized, array('allowed_classes' => false));
}

Matched route "home" results in Token was deauthenticated

Following 4.1 docs, with a mash-up of the two User entities How to Load Security Users and How to Implement a Simple Registration Form, attempts to log in result in the following dev log entries:
security.INFO: User has been authenticated successfully...
security.DEBUG: Stored the security token in the session...
request.INFO: Matched route "home"...
security.DEBUG: Read existing security token from the session...
doctrine.DEBUG: SELECT t0.id AS id_1,...
security.DEBUG: Token was deauthenticated after trying to refresh it
User entity (includes this SO answer)
class User implements UserInterface, \Serializable, EquatableInterface
{
public function __construct()
{
$this->roles = array('ROLE_USER');
$this->isActive = true;
}
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="is_active", type="boolean")
*/
private $isActive;
/**
* #ORM\Column(type="string", length=255, unique=true)
* #Assert\NotBlank()
* #Assert\Email()
*/
private $email;
/**
* #ORM\Column(type="string", length=255, unique=true)
* #Assert\NotBlank()
*/
private $username;
/**
* #Assert\NotBlank()
* #Assert\Length(max=4096)
*/
private $plainPassword;
/**
* The below length depends on the "algorithm" you use for encoding
* the password, but this works well with bcrypt.
*
* #ORM\Column(type="string", length=64)
*/
private $password;
/**
* #ORM\Column(type="array")
*/
private $roles;
// other properties and methods
public function getEmail()
{
return $this->email;
}
public function setEmail($email)
{
$this->email = $email;
}
public function getUsername()
{
return $this->username;
}
public function setUsername($username)
{
$this->username = $username;
}
public function getPlainPassword()
{
return $this->plainPassword;
}
public function setPlainPassword($password)
{
$this->plainPassword = $password;
}
public function getPassword()
{
return $this->password;
}
public function setPassword($password)
{
$this->password = $password;
}
public function getSalt()
{
return null;
}
public function eraseCredentials()
{
}
public function getRoles()
{
return $this->roles;
}
public function addRole($role)
{
$role = strtoupper($role);
if ($role === static::ROLE_DEFAULT) {
return $this;
}
if (!in_array($role, $this->roles, true)) {
$this->roles[] = $role;
}
return $this;
}
public function setActive($boolean)
{
$this->active = (bool) $boolean;
return $this;
}
/** #see \Serializable::serialize() */
public function serialize()
{
return serialize(array(
$this->id,
$this->username,
$this->password,
// see section on salt below
// $this->salt,
));
}
/** #see \Serializable::unserialize() */
public function unserialize($serialized)
{
list (
$this->id,
$this->username,
$this->password,
// see section on salt below
// $this->salt
) = unserialize($serialized, ['allowed_classes' => false]);
}
public function isEqualTo(UserInterface $user)
{
if ($this->password !== $user->getPassword()) {
return false;
}
if ($this->username !== $user->getUsername()) {
return false;
}
return true;
}
}
security.yaml:
security:
encoders:
App\Entity\User:
algorithm: bcrypt
providers:
db_provider:
entity:
class: App\Entity\User
property: username
firewalls:
main:
provider: db_provider
anonymous: ~
form_login:
login_path: login
check_path: login
default_target_path: home
access_control:
- { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: ROLE_USER }
Controller:
public function login(Request $request, AuthenticationUtils $authenticationUtils)
{
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig', array(
'last_username' => $lastUsername,
'error' => $error,
));
}
So after a long discussion it was determined that roles were not being set in the database. Attempts to set one in the constructor were doomed to failure since Doctrine does not use the constructor when hydrating from the database.
The proper solution is to correctly store the roles in the database. A bit of a hack is to do something like:
// User
getRoles() {
return count($roles) ? $roles : ['ROLE_USER'];
}
And if you look closely, the addRoles method in the question has an error probably because it was partially cloned from the FOSUserBundle. Always a dangerous thing to do.

Undefined class constant 'ROLE_DEFAULT' when register new user SYMFONY 4

I'm trying to build a login/register controller for users but i get Underfined class constant 'ROLE_DEFAULT' when I register new user.
In my UserController, I have this function to register new user:
//src/Controller/UserController
public function registerAction(Request $request, UserPasswordEncoderInterface $passwordEncoder)
{
// 1) build the form
$user = new User();
$form = $this->createForm(UserType::class, $user);
// 2) handle the submit (will only happen on POST)
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// 3) Encode the password (you could also do this via Doctrine listener)
$password = $passwordEncoder->encodePassword($user, $user->getPlainPassword());
$user->setPassword($password);
$apiKey = $passwordEncoder->encodePassword($user, rand(1,9999));
$user->setApiKey($apiKey);
$user->setEnabled(1);
// $user->setSuperadmin(true);
$user->addRole("ROLE_DEFAULT");
// 4) save the User!
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($user);
$entityManager->flush();
dump($user);
exit;
}
return $this->render(
'user/register.html.twig',
array('form' => $form->createView())
);
}
The error appear when "$user->addRole("ROLE_DEFAULT");" and this is the screenshot:
//src/Entity/User:
namespace App\Entity;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="user")
*/
class User implements UserInterface
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", unique=true)
*/
private $username;
/**
* #ORM\Column(name="lastname", type="string", nullable=true)
*/
protected $lastname;
/**
* #ORM\Column(type="string", unique=true)
*/
private $apiKey;
/**
* #ORM\Column(name="enabled", type="boolean")
*/
protected $enabled;
/**
* #ORM\Column(name="email", type="string", nullable=false)
*/
protected $email;
/**
* #ORM\Column(name="SUPERADMIN", type="boolean", nullable=true)
*/
protected $superadmin;
/**
* The salt to use for hashing
*
* #ORM\Column(name="salt", type="string", nullable=true)
*/
protected $salt;
/**
* Encrypted password. Must be persisted.
*
* #ORM\Column(name="password", type="string", nullable=false)
*/
protected $password;
/**
* Encrypted password. Must be persisted.
*
* #ORM\Column(name="plainPassword", type="string", nullable=false)
*/
protected $plainPassword;
/**
* #var \DateTime
*/
protected $lastLogin;
/**
* Random string sent to the user email address in order to verify it
*
* #var string
*/
protected $confirmationToken;
/**
* #var \DateTime
*/
protected $passwordRequestedAt;
public function __construct()
{
$this->salt = base_convert(sha1(uniqid(mt_rand(), true)), 16, 36);
$this->enabled = false;
// $this->locked = false;
$this->roles = array();
}
public function __toString()
{
return (string) $this->getUsername();
}
public function getId()
{
return $this->id;
}
// USERNAME//
//===========================================================
public function setUsername($username)
{
$this->username = $username;
return $this;
}
public function getUsername()
{
return $this->username;
}
// EMAIL//
//===========================================================
public function getEmail()
{
return $this->email;
}
public function setEmail($email)
{
$this->email = $email;
return $this;
}
// API KEY//
//===========================================================
public function getApiKey()
{
return $this->apiKey;
}
public function setApiKey($apiKey)
{
return $this->apiKey = $apiKey;
}
// PASSWORD//
//===========================================================
/**
* Gets the encrypted password.
*
* #return string
*/
public function getPassword()
{
return $this->password;
}
public function setPassword($password)
{
$this->password = $password;
return $this;
}
/**
* Gets the encrypted password.
*
* #return string
*/
public function getPlainPassword()
{
return $this->plainPassword;
}
public function setPlainPassword($password)
{
$this->plainPassword = $password;
}
// SALT//
//===========================================================
public function getSalt()
{
return $this->salt;
}
public function setSalt($salt)
{
return $this->salt = $salt;
}
// LAST LOGIN//
//===========================================================
/**
* Gets the last login time.
*
* #return \DateTime
*/
public function getLastLogin()
{
return $this->lastLogin;
}
public function setLastLogin(\DateTime $time = null)
{
$this->lastLogin = $time;
return $this;
}
// CONFIRMATION TOKEN//
//===========================================================
public function getConfirmationToken()
{
return $this->confirmationToken;
}
public function setConfirmationToken($confirmationToken)
{
$this->confirmationToken = $confirmationToken;
return $this;
}
/**
* Removes sensitive data from the user.
*/
public function eraseCredentials()
{
$this->plainPassword = null;
}
// ROLES//
//===========================================================
// public function getRoles()
// {
// return array('ROLE_USER');
// }
/**
* Returns the user roles
*
* #return array The roles
*/
public function getRoles()
{
$roles = array();
$nameRole = $this->idRole->getNamerole();
$stationCreate = $this->idRole->getStationCreate();
$configurationstationEdit = $this->idRole->getConfigurationstationEdit();
$stationEditor = $this->idRole->getDatastationEdit();
$stationValidate = $this->idRole->getStationValidate();
$newdataCreate = $this->idRole->getNewdataCreate();
$superadmin = $this->superadmin;
if($stationCreate){
$roles = array_merge($roles, array('ROLE_CREATOR'));
}
if($configurationstationEdit){
$roles = array_merge($roles, array('ROLE_CONFIGURATOR'));
}
if($stationEditor){
$roles = array_merge($roles, array('ROLE_EDITOR'));
}
if($stationValidate){
$roles = array_merge($roles, array('ROLE_VALIDATOR'));
}
if($newdataCreate){
$roles = array_merge($roles, array('ROLE_MANAGE'));
}
if($superadmin){
$roles = array_merge($roles, array('ROLE_SUPER_ADMIN'));
}
//echo $nameRole." -- ".$stationCreate." -- ".$configurationstationEdit." -- ".$stationValidate." -- ".$newdataCreate;
//exit;
//dump($this->idRole);
//exit;
// we need to make sure to have at least one role
$roles[] = static::ROLE_DEFAULT;
return array_unique($roles);
}
public function setRoles(array $roles)
{
$this->roles = array();
foreach ($roles as $role) {
$this->addRole($role);
}
return $this;
}
public function removeRole($role)
{
if (false !== $key = array_search(strtoupper($role), $this->roles, true)) {
unset($this->roles[$key]);
$this->roles = array_values($this->roles);
}
return $this;
}
public function hasRole($role)
{
return in_array(strtoupper($role), $this->getRoles(), true);
}
public function addRole($role)
{
$role = strtoupper($role);
if ($role === static::ROLE_DEFAULT) {
return $this;
}
if (!in_array($role, $this->roles, true)) {
$this->roles[] = $role;
}
return $this;
}
// ENABLED//
//===========================================================
public function isEnabled()
{
return $this->enabled;
}
public function setEnabled($boolean)
{
$this->enabled = (Boolean) $boolean;
return $this;
}
// SUPERADMIN//
//===========================================================
public function isSuperAdmin()
{
return $this->hasRole(static::ROLE_SUPER_ADMIN);
}
public function setSuperAdmin($boolean)
{
if (true === $boolean) {
$this->addRole(static::ROLE_SUPER_ADMIN);
} else {
$this->removeRole(static::ROLE_SUPER_ADMIN);
}
return $this;
}
// PASSWORD REQUEST AT//
//===========================================================
/**
* Gets the timestamp that the user requested a password reset.
*
* #return null|\DateTime
*/
public function getPasswordRequestedAt()
{
return $this->passwordRequestedAt;
}
public function isPasswordRequestNonExpired($ttl)
{
return $this->getPasswordRequestedAt() instanceof \DateTime &&
$this->getPasswordRequestedAt()->getTimestamp() + $ttl > time();
}
// EXTRA FUNCTIONS//
//===========================================================
/**
* Serializes the user.
*
* The serialized data have to contain the fields used during check for
* changes and the id.
*
* #return string
*/
public function serialize()
{
return serialize(array(
$this->password,
$this->salt,
$this->username,
$this->enabled,
$this->id,
$this->email,
));
}
/**
* Unserializes the user.
*
* #param string $serialized
*/
public function unserialize($serialized)
{
$data = unserialize($serialized);
// add a few extra elements in the array to ensure that we have enough keys when unserializing
// older data which does not include all properties.
$data = array_merge($data, array_fill(0, 2, null));
list(
$this->password,
$this->salt,
$this->username,
$this->enabled,
$this->id,
$this->email,
) = $data;
}
}
And the security.yml file
//config/packages/security.yml
security:
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
encoders:
App\Entity\User:
algorithm: bcrypt
providers:
in_memory: { memory: ~ }
user:
entity:
class: App\Entity\User
# property: apiKey
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
pattern: ^/
http_basic: ~
provider: user
# activate different ways to authenticate
# http_basic: true
# https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate
# form_login: true
# https://symfony.com/doc/current/security/form_login_setup.html
# form_login:
# login_path: security_login
# check_path: security_login
# csrf_token_generator: security.csrf.token_manager
# default_target_path: userRedirectAction
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/user, roles: ROLE_USER }
I want to know what I'm doing wrong or where can i find a good guide to build a login/register system in SYMFONY 4.
Thank you.
The first thing that I noticed is use of static to (presumably) get constant. If that was the intention, it is not right.
See this comment of PHP constatnt official docs to get a grasp of what could go wrong:
PHP Constant - self/static
But, regardless, what I could not see was the constant itself defined within the User class, which I saw belonged to your namespace.
So, in order to use it, please define it like:
class User
{
const ROLE_DEFAULT = "ROLE_DEFAULT";
// ....
// The rest of your code
}
And then use it like:
if ($role === self::ROLE_DEFAULT) {
return $this;
}
Though, I am not sure why would you attempt to add that role in your controller at the first place, however, I leave to you to you to decide.
Hope this helps a bit...

Categories