createnl/zxcvbn-bundle
Symfony bundle integrating zxcvbn-php for password strength scoring with user-data hints, localized feedback (EN/NL/FR), and support for custom matchers. Provides a factory service to create a Zxcvbn instance for easy use in controllers and services.
Install the Bundle
composer require createnl/zxcvbn-bundle
Ensure Createnl\ZxcvbnBundle\CreatenlZxcvbnBundle is enabled in config/bundles.php.
First Use Case: Password Strength Validation
Inject ZxcvbnFactoryInterface into your controller/service and use it to evaluate password strength:
use Createnl\ZxcvbnBundle\ZxcvbnFactoryInterface;
public function checkPassword(ZxcvbnFactoryInterface $zxcvbnFactory, string $password) {
$zxcvbn = $zxcvbnFactory->createZxcvbn();
$result = $zxcvbn->passwordStrength($password, ['user_data' => ['username', 'email']]);
return $result['score']; // 0-4
}
Localization
en, nl, fr.config/packages/createnl_zxcvbn.yaml:
createnl_zxcvbn:
locale: 'fr' # or 'nl'
Frontend Integration
Use JavaScript (e.g., zxcvbn-js) for client-side feedback, then validate server-side with this bundle.
Example:
// In a Symfony Form Type
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('password', PasswordType::class, [
'constraints' => [
new CallbackConstraint(function ($password, ExecutionContextInterface $context) {
$zxcvbn = $this->zxcvbnFactory->createZxcvbn();
$result = $zxcvbn->passwordStrength($password, ['user_data' => ['username']]);
if ($result['score'] < 3) {
$context->buildViolation('Password too weak')
->atPath('password')
->addViolation();
}
})
]
]);
}
Batch Processing User Data
For bulk operations (e.g., password resets), cache Zxcvbn instances:
// Cache the factory for reuse
$zxcvbn = $zxcvbnFactory->createZxcvbn();
foreach ($users as $user) {
$result = $zxcvbn->passwordStrength($newPassword, ['user_data' => [$user->username]]);
// Store result or trigger actions
}
Custom Feedback Handling Extract feedback for UI display:
$result = $zxcvbn->passwordStrength($password);
$feedback = [
'score' => $result['score'],
'warning' => $result['feedback']['warning'] ?? null,
'suggestions' => $result['feedback']['suggestions'] ?? [],
];
return $this->json($feedback);
User Data Sensitivity
passwordStrength().$userData = [
'username' => $user->username,
'email' => hash('sha256', $user->email), // Hash sensitive fields
];
Performance with Large Datasets
Zxcvbn instance in a warmup command:
// bin/console zxcvbn:warmup
public function handle() {
$factory = $this->container->get(ZxcvbnFactoryInterface::class);
$factory->createZxcvbn(); // Trigger lazy loading
}
Localization Quirks
# config/packages/createnl_zxcvbn.yaml
createnl_zxcvbn:
locale: 'es' # Unofficial; will use English fallback
Score Discrepancies
zxcvbn-dictionary).Matcher Conflicts
zxcvbn.matcher may override default behavior. Test isolated matchers:
// Disable all matchers except your custom one
$zxcvbn = $zxcvbnFactory->createZxcvbn(['matchers' => [YourMatcher::class]]);
Custom Matchers
zxcvbn-php's matchers by implementing zxcvbn\MatcherInterface:
use zxcvbn\MatcherInterface;
class CustomMatcher implements MatcherInterface {
public function match($password, $userInputs) {
return ['pattern': 'custom', 'score': 1, 'token': []];
}
}
services.yaml:
services:
App\CustomMatcher:
tags: ['zxcvbn.matcher']
Configuration Overrides
zxcvbn options (e.g., guesses, feedback):
createnl_zxcvbn:
options:
guesses: 1000000000000 # Adjust for stricter/looser scoring
Symfony Event Integration
// src/EventListener/ZxcvbnListener.php
public function onPasswordSubmit(PasswordEvent $event) {
$zxcvbn = $this->zxcvbnFactory->createZxcvbn();
$result = $zxcvbn->passwordStrength($event->getPassword());
if ($result['score'] < 3) {
$event->setError('Weak password');
}
}
$event = new PasswordEvent($password);
$this->eventDispatcher->dispatch($event);
if ($event->hasError()) { /* Handle */ }
Combine with Symfony Validator Use the bundle’s score in a custom constraint:
use Symfony\Component\Validator\Constraint;
class MinPasswordStrength extends Constraint {
public $score;
public function validatedBy() { return static::class; }
public function validate($value, ExecutionContext $context) {
$result = $this->zxcvbnFactory->createZxcvbn()->passwordStrength($value);
if ($result['score'] < $this->score) {
$context->buildViolation($this->message)
->addViolation();
}
}
}
Logging Feedback Log password strength results (anonymized) for analytics:
$logger->info('Password strength', [
'score' => $result['score'],
'user_id' => $user->id, // Avoid logging sensitive data
]);
Testing
Mock ZxcvbnFactoryInterface in tests:
$mockFactory = $this->createMock(ZxcvbnFactoryInterface::class);
$mockZxcvbn = $this->createMock(Zxcvbn::class);
$mockZxcvbn->method('passwordStrength')->willReturn(['score' => 4]);
$mockFactory->method('createZxcvbn')->willReturn($mockZxcvbn);
$this->controller->setContainer($this->createMock(ContainerInterface::class));
$this->controller->setZxcvbnFactory($mockFactory);
How can I help you explore Laravel packages today?