Symfony / Api-platform updates rehash password on put request gives error when login in again
I have the exact same issue as this person, looks like he found out the solution because he says "My bad I didn't follow the documentation quite thoroughly. It's not an issue anymore" in the comments.
<?php
namespace App\Entity;
use ApiPlatform\Core\Action\NotFoundAction;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Repository\UserRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
use App\Controller\MeController;
use App\Controller\ForgottenPasswordController;
use App\Controller\UpdateUser;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: '`user`')]
/**
* Un utilisateur
*
* @UniqueEntity(fields={"email"}, message="Il y a d?�j?� un compte avec cet email")
*/
#[ApiResource(
collectionOperations: [
"get" => [
"security" => "is_granted('ROLE_ADMIN')",
"security_message" => "Vous n'avez pas acc??s ?� cette ressource."
],
"post",
"me" => [
"path" => "/me",
"method" => "get",
"controller" => MeController::class,
],
"forgotten_password" => [
"method" => "post",
"path" => "/forgotten-password",
"controller" => ForgottenPasswordController::class,
"denormalization_context" => ["groups" => ["forgotten-password"]]
],
],
itemOperations: [
"get" => [
"security" => "is_granted('ROLE_ADMIN')",
"security_message" => "Vous n'avez pas acc??s ?� cette ressource."
],
// "put" => [
// "security" => "object == user or is_granted('ROLE_ADMIN')",
// "security_message" => "Vous n'avez pas acc??s ?� cette ressource."
// ],
"update_user" => [
"method" => "PUT",
"path" => "/users/{id}/update",
"controller" => UpdateUser::class,
"normalization_context"=>["groups"=>["user_update"]]
],
"delete" => [
"security" => "object == user or is_granted('ROLE_ADMIN')",
"security_message" => "Vous n'avez pas acc??s ?� cette ressource."
],
// "patch" => [
// "security" => "is_granted('ROLE_ADMIN')",
// "security_message" => "Vous n'avez pas acc??s ?� cette ressource."
// ],
// "update_password" => [
// "method" => "PUT",
// "path" => "users/{id}/update-password",
//// "controller"=> App\Controller\UserChangePassword::class,
//// "normalization_context"=>{"groups"={"user:passwordUpdate"}}
// ]
],
denormalizationContext: [
'groups' => ['user:input']
],
normalizationContext: [
'groups' => ['user:output']
]
)]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
#[Groups(['user:output'])]
/**
* L'identifiant d'un utilisateur
*/
private $id;
#[ORM\Column(type: 'string', length: 180, unique: true)]
#[Groups(['user:output', 'user:input', 'forgotten-password', 'read:command', 'user_update'])]
/**
* L'email d'un utilisateur
*
* @Assert\NotNull
* @Assert\Email(
* message = "L'email'{{ value }}' n'est pas valide."
* )
*/
private $email;
#[ORM\Column(type: 'json')]
#[Groups(['user:output', 'admin:input'])]
/** Le role d'un utilisateur */
private $roles = [];
#[ORM\Column(type: 'string')]
#[Groups(['user:input'])]
/**
* Le mot de passe d'un utilisateur
*
* @Assert\NotNull
* @Assert\NotBlank(
* normalizer = "trim"
* )
*
*/
private $password;
#[ORM\Column(type: 'string', length: 255)]
#[Groups(['user:output', 'user:input'])]
/**
* Le pr?�nom d'un utilisateur
*
* @Assert\NotNull
* @Assert\NotBlank(
* normalizer = "trim"
* )
* @Assert\Length(
* min = 1,
* max = 50,
* minMessage = "Doit contenir {{ limit }} caract??res minimum",
* maxMessage = "Doit contenir {{ limit }} caract??res maximum"
* )
*/
private $firstname;
#[ORM\Column(type: 'string', length: 255)]
#[Groups(['user:output', 'user:input'])]
/**
* Le nom d'un utilisateur
*
* @Assert\NotNull
* @Assert\NotBlank(
* normalizer = "trim"
* )
* @Assert\Length(
* min = 1,
* max = 50,
* minMessage = "Doit contenir {{ limit }} caract??res minimum",
* maxMessage = "Doit contenir {{ limit }} caract??res maximum"
* )
*
*/
private $lastname;
#[ORM\OneToMany(mappedBy: 'owner', targetEntity: Command::class, orphanRemoval: true)]
#[Groups(['user:output'])]
/** Les commandes effectu?�es par un utilisateur */
private $commands;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
#[Groups(['user:output', 'user:input'])]
/** L'adresse d'un utilisateur
*
* @Assert\NotBlank(
* normalizer = "trim"
* )
* @Assert\Length(
* min = 1,
* max = 255,
* minMessage = "Doit contenir {{ limit }} caract??res minimum",
* maxMessage = "Doit contenir {{ limit }} caract??res maximum"
* )
*
*/
private $address;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private $stripeToken;
#[ORM\Column(type: 'boolean')]
private $isVerified = false;
#[ORM\Column(type: 'datetime', nullable: true)]
private $tokenValidAfter;
public function __construct()
{
$this->commands = 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;
}
/**
* Un identifiant visuel qui repr?�sente cet utilisateur
*
* @see UserInterface
*/
public function getUserIdentifier(): string
{
return (string) $this->email;
}
/**
* @see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* @see PasswordAuthenticatedUserInterface
*/
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* @see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
public function getFirstname(): ?string
{
return $this->firstname;
}
public function setFirstname(string $firstname): self
{
$this->firstname = $firstname;
return $this;
}
public function getLastname(): ?string
{
return $this->lastname;
}
public function setLastname(string $lastname): self
{
$this->lastname = $lastname;
return $this;
}
/**
* @return Collection|Command[]
*/
public function getCommands(): Collection
{
return $this->commands;
}
public function addCommand(Command $command): self
{
if (!$this->commands->contains($command)) {
$this->commands[] = $command;
$command->setOwner($this);
}
return $this;
}
public function removeCommand(Command $command): self
{
if ($this->commands->removeElement($command)) {
// set the owning side to null (unless already changed)
if ($command->getOwner() === $this) {
$command->setOwner(null);
}
}
return $this;
}
public function getAddress(): ?string
{
return $this->address;
}
public function setAddress(?string $address): self
{
$this->address = $address;
return $this;
}
public function getStripeToken(): ?string
{
return $this->stripeToken;
}
public function setStripeToken(?string $stripeToken): self
{
$this->stripeToken = $stripeToken;
return $this;
}
public function getIsVerified(): ?bool
{
return $this->isVerified;
}
public function setIsVerified(bool $isVerified): self
{
$this->isVerified = $isVerified;
return $this;
}
public function getTokenValidAfter(): ?\DateTimeInterface
{
return $this->tokenValidAfter;
}
public function setTokenValidAfter(?\DateTimeInterface $tokenValidAfter): self
{
$this->tokenValidAfter = $tokenValidAfter;
return $this;
}
}
<?php
namespace App\DataPersister;
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class UserDataPersister implements DataPersisterInterface
{
private $entityManager;
private $userPasswordEncoder;
public function __construct(EntityManagerInterface $entityManager, UserPasswordHasherInterface $userPasswordEncoder)
{
$this->entityManager = $entityManager;
$this->userPasswordEncoder = $userPasswordEncoder;
}
public function supports($data): bool
{
return $data instanceof User;
}
/**
* @param User $data
*/
public function persist($data)
{
if ($data->getPassword()) {
$data->setPassword(
$this->userPasswordEncoder->hashPassword($data, $data->getPassword())
);
$data->eraseCredentials();
}
$this->entityManager->persist($data);
$this->entityManager->flush();
}
public function remove($data)
{
$this->entityManager->remove($data);
$this->entityManager->flush();
}
}
Your problem is due to the fact that your code hash an already hashed password and so generate a new password each time.
You have to add and use a plainPassword property.
use Symfony\Component\Serializer\Annotation\SerializedName;
...
collectionOperations: [
...
"post" => ['validation_groups' => ['Default', 'postValidation']]
...
#[ORM\Column(type: 'string')]
private $password;
#[Groups("user:input")]
#[SerializedName("password")]
#[Assert\NotBlank(normalizer: 'trim', groups: ['postValidation'])]
private $plainPassword;
...
// getter/setter plainPassword
...
And in your UserDataPersister, use this plainPassword instead.
public function persist($data)
{
if (null !== $plainPassword = $data->getPlainPassword()) {
$data->setPassword(
$this->userPasswordEncoder->hashPassword($data, $plainPassword)
);
$data->eraseCredentials();
}
...
Our community is visited by hundreds of web development professionals every day. Ask your question and get a quick answer for free.
Find the answer in similar questions on our website.
Do you know the answer to this question? Write a quick response to it. With your help, we will make our community stronger.
PHP (from the English Hypertext Preprocessor - hypertext preprocessor) is a scripting programming language for developing web applications. Supported by most hosting providers, it is one of the most popular tools for creating dynamic websites.
The PHP scripting language has gained wide popularity due to its processing speed, simplicity, cross-platform, functionality and distribution of source codes under its own license.
https://www.php.net/
Symfony compares favorably with other PHP frameworks in terms of reliability and maturity. This framework appeared a long time ago, in 2005, that is, it has existed much longer than most of the other tools we are considering. It is popular for its web standards compliance and PHP design patterns.
https://symfony.com/
Welcome to the Q&A site for web developers. Here you can ask a question about the problem you are facing and get answers from other experts. We have created a user-friendly interface so that you can quickly and free of charge ask a question about a web programming problem. We also invite other experts to join our community and help other members who ask questions. In addition, you can use our search for questions with a solution.
Ask about the real problem you are facing. Describe in detail what you are doing and what you want to achieve.
Our goal is to create a strong community in which everyone will support each other. If you find a question and know the answer to it, help others with your knowledge.