April 5, 2019 Javier Eguiluz

A data breach is the intentional or unintentional release of secure or private/confidential information to an untrusted environment. The list of data breaches increases every day and, just in the first half of 2018, about 4.5 billion records were exposed, including user passwords.

Users that set their password to any of the publicly exposed passwords are a serious security problem for web sites and applications. That's why services like have i been pwned? allow you to check if your password is compromised.

In Symfony 4.3, we've added a new NotCompromisedPassword constraint to validate that the given password hasn't been compromised:

Annotations 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // src/Entity/User.php namespace App\Entity ; use Symfony\Component\Validator\Constraints as Assert ; class User { // ... /** * @Assert\NotCompromisedPassword */ protected $rawPassword ; }

YAML 1 2 3 4 5 # config/validator/validation.yaml App\Entity\User : properties : rawPassword : - NotCompromisedPassword

XML 1 2 3 4 5 6 7 8 9 10 11 12 <!-- config/validator/validation.xml --> <?xml version="1.0" encoding="UTF-8" ?> <constraint-mapping xmlns= "http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation= "http://symfony.com/schema/dic/constraint-mapping https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd" > <class name= "App\Entity\User" > <property name= "rawPassword" > <constraint name= "NotCompromisedPassword" ></constraint> </property> </class> </constraint-mapping>

PHP 1 2 3 4 5 6 7 8 9 10 11 12 13 // src/Entity/User.php namespace App\Entity ; use Symfony\Component\Validator\Mapping\ClassMetadata ; use Symfony\Component\Validator\Constraints as Assert ; class User { public static function loadValidatorMetadata ( ClassMetadata $metadata ) { $metadata -> addPropertyConstraint ( 'rawPassword' , new Assert\NotCompromisedPassword ()); } }

Internally, the constraint makes an HTTP request to the API provided by the haveibeenpwned.com website. In the request, the validator doesn't send the raw password but only the few first characters of the result of encoding it using SHA-1.

For example, if the raw password is test , the SHA-1 hash is a94a8fe5ccb19ba61c4c0873d391e987982fbbd3 and the validator only sends a94a8 to haveibeenpwned.com (the first five characters of the SHA-1 hash). This is called "k-anonymity password validation" and is fully explained in this blog post by Cloudflare.

The HTTP request is made with the new HttpClient component added in Symfony 4.3 and which will we introduced soon in a dedicated blog post.