Hashing password in an Event Engine application
For a few projects we make use of the Event Engine by prooph software which is a biased - in a good way(tm) - way of quickly building event sourced applications. While building a user registration context, I was stuck a bit how and where to properly hash the password for the user. I could have done it in the init() method of the command, that way I would have made sure that the cleartext password is not "visible" anywhere in the application code. In the end, I decided to make use of a provider class. The return value of the provider will be passed to the specified aggregate method and allows me to pass it to the respective event.
First, I created a marker interface for the commands to make sure that only commands that implement the interface can be passed to the provider class:
interface HasUnhashedPassword
{
/**
* @return UnhashedPassword
*/
public function password(): ?UnhashedPassword;
}
As you can see, the password() method returns an UnhashedPassword value object (or null in case no password was provided). Besides having an UnhashedPassword value object, I have also created a HashedPassword value object that gets returned by the provider class. Having a clear distinction between unhashed and hashed passwords makes sense and helps to communicate the intent of method parameters or return values better.
As the next step, a Password Hasher is needed which contains the logic to turn an unhashed password into a HashedPassword. The concrete implementation does not matter now, let's have a look at the interface:
interface PasswordHasher
{
/** Encrypts the given password */
public function createHash(UnhashedPassword $password): HashedPassword;
/** Verifies the given plain text password against the hashed one */
public function verify(UnhashedPassword $password,
HashedPassword $hashedPassword): bool;
}
Password hasher instances get injected into the HashedPasswordProvider - the provider class defined by the Event Engine:
final class HashedPasswordProvider
{
/** @var PasswordHasher */
private $passwordHasherer;
public function __construct(PasswordHasher $passwordHasherer)
{
$this->passwordHasherer = $passwordHasherer;
}
public function __invoke(HasUnhashedPassword $command): HashedPassword
{
return HashedPassword::createFromUnhashedPasswordWithHasher(
$command->password(), $this->passwordHasherer
);
}
}
To register the provider class add a provideContext() method call to the $eventEngine configuration and pass the class name of the HashedPasswordProvider implementation. The Event Engine will take care of executing the provider class and passing the return value to the aggregate:
$eventEngine->process(Command::ATTENDEE_REGISTER)
->withNew(self::ATTENDEE)
->identifiedBy(Payload::ATTENDEE_ID)
->provideContext(HashedPasswordProvider::class)
->handle([Attendee::class, 'register'])
->recordThat(Event::ATTENDEE_REGISTERED)
->apply([Attendee::class, 'whenRegistered'])
->storeStateIn(AttendeeResolver::COLLECTION);
The aggregate implementation looks like this:
final class Attendee
{
public static function register(RegisterAttendee $command,
HashedPassword $hashedPassword): Generator
{
$attendee = $command->toArray();
$attendee[Payload::ATTENDEE_PASSWORD] = $hashedPassword->toString();
yield Registered::fromArray($attendee);
}
}
This way the aggregate has access to the command object as well as the hashed password and can do whatever needs to be done.