Skip to main content

Password Validation

The PasswordDefinition class provides comprehensive password strength validation and generation capabilities.

Basic Usage​

Creating a Password Definition​

<?php
use ByJG\Authenticate\Definition\PasswordDefinition;
use ByJG\Authenticate\Model\UserModel;

// Create password definition with default rules
$passwordDefinition = new PasswordDefinition();

// Attach to user model
$userModel = new UserModel();
$userModel->withPasswordDefinition($passwordDefinition);

// Now password is validated when set
$userModel->setPassword('WeakPwd'); // Throws InvalidArgumentException

Applying Rules Through UsersRepository​

Instead of attaching the definition to every UserModel manually, pass it to the UsersRepository constructor. The repository is where every user is written, so it owns the rules:

  • It sets a PasswordDefinitionValidator on its MicroORM Repository. Every save, through UsersService, UsersRepository or the MicroORM Repository directly, also of a model created with new, checks a plain password against the definition and throws InvalidArgumentException when it does not match. A stored hash is never checked.
  • A validator the user model declares (#[TableAttribute(validator: ...)]) still runs after it; one set later with setValidator() replaces it.
  • UsersService takes the definition from the repository (getPasswordDefinition()) and gives it to every entity it creates or loads (addUser(), getUsersEntity(), getById(), ...), so setPassword() rejects a weak password right away, before the save.
<?php
use ByJG\Authenticate\Definition\PasswordDefinition;
use ByJG\Authenticate\Enum\LoginField;
use ByJG\Authenticate\Model\UserModel;
use ByJG\Authenticate\Repository\UsersRepository;
use ByJG\Authenticate\Service\UsersService;

$passwordDefinition = new PasswordDefinition([
PasswordDefinition::MINIMUM_CHARS => 12,
PasswordDefinition::REQUIRE_NUMBERS => 2,
]);

$usersRepo = new UsersRepository($executor, UserModel::class, $passwordDefinition);

$users = new UsersService(
$usersRepo,
$propsRepo,
LoginField::Username
);

$newUser = $users->addUser('Jane', 'jane', '[email protected]', 'StrongPassword123!');

Need a blank entity with the validation rules already loaded? Use getUsersEntity():

<?php
$user = $users->getUsersEntity([
'name' => 'John Doe',
'email' => '[email protected]',
]);
$user->setPassword('AnotherStrongPass123!');
$users->save($user);

This approach guarantees that every model created or retrieved through the service (getById(), getByEmail(), getUsersByProperty(), etc.) enforces your password policy when its password is set, and the repository enforces it again on every save.

Password Rules​

Default Rules​

The default password policy requires:

RuleDefault ValueDescription
minimum_chars8Minimum password length
require_uppercase0Number of uppercase letters required
require_lowercase1Number of lowercase letters required
require_symbols0Number of symbols required
require_numbers1Number of digits required
allow_whitespace0Allow whitespace characters (0 = no)
allow_sequential0Allow sequential characters (0 = no)
allow_repeated0Allow repeated patterns (0 = no)

Custom Rules​

<?php
use ByJG\Authenticate\Definition\PasswordDefinition;

$passwordDefinition = new PasswordDefinition([
PasswordDefinition::MINIMUM_CHARS => 12,
PasswordDefinition::REQUIRE_UPPERCASE => 2,
PasswordDefinition::REQUIRE_LOWERCASE => 2,
PasswordDefinition::REQUIRE_SYMBOLS => 1,
PasswordDefinition::REQUIRE_NUMBERS => 2,
PasswordDefinition::ALLOW_WHITESPACE => 0,
PasswordDefinition::ALLOW_SEQUENTIAL => 0,
PasswordDefinition::ALLOW_REPEATED => 0
]);

Setting Individual Rules​

<?php
$passwordDefinition = new PasswordDefinition();
$passwordDefinition->setRule(PasswordDefinition::MINIMUM_CHARS, 10);
$passwordDefinition->setRule(PasswordDefinition::REQUIRE_UPPERCASE, 1);
$passwordDefinition->setRule(PasswordDefinition::REQUIRE_SYMBOLS, 1);

Validating Passwords​

Validation Result Codes​

The matchPassword() method returns a bitwise result:

<?php
$result = $passwordDefinition->matchPassword('weak');

if ($result === PasswordDefinition::SUCCESS) {
echo "Password is valid";
} else {
// Check specific failures
if ($result & PasswordDefinition::FAIL_MINIMUM_CHARS) {
echo "Password is too short\n";
}
if ($result & PasswordDefinition::FAIL_UPPERCASE) {
echo "Missing uppercase letters\n";
}
if ($result & PasswordDefinition::FAIL_LOWERCASE) {
echo "Missing lowercase letters\n";
}
if ($result & PasswordDefinition::FAIL_NUMBERS) {
echo "Missing numbers\n";
}
if ($result & PasswordDefinition::FAIL_SYMBOLS) {
echo "Missing symbols\n";
}
if ($result & PasswordDefinition::FAIL_WHITESPACE) {
echo "Whitespace not allowed\n";
}
if ($result & PasswordDefinition::FAIL_SEQUENTIAL) {
echo "Sequential characters detected\n";
}
if ($result & PasswordDefinition::FAIL_REPEATED) {
echo "Repeated patterns detected\n";
}
}

Available Failure Codes​

ConstantValueDescription
SUCCESS0Password is valid
FAIL_MINIMUM_CHARS1Password too short
FAIL_UPPERCASE2Missing uppercase letters
FAIL_LOWERCASE4Missing lowercase letters
FAIL_SYMBOLS8Missing symbols
FAIL_NUMBERS16Missing numbers
FAIL_WHITESPACE32Whitespace not allowed
FAIL_SEQUENTIAL64Sequential characters detected
FAIL_REPEATED128Repeated patterns detected

Password Generation​

Generate a Random Password​

<?php
$password = $passwordDefinition->generatePassword();
echo $password; // e.g., "aB3dE7fG9"

Generate Longer Passwords​

<?php
// Generate a password 5 characters longer than the minimum
$password = $passwordDefinition->generatePassword(5);

The generated password will:

  • Meet all defined rules
  • Be cryptographically random
  • Avoid sequential and repeated patterns

User Registration with Password Validation​

Complete Example​

<?php
use ByJG\Authenticate\Definition\PasswordDefinition;
use ByJG\Authenticate\Model\UserModel;

// Define password rules
$passwordDefinition = new PasswordDefinition([
PasswordDefinition::MINIMUM_CHARS => 10,
PasswordDefinition::REQUIRE_UPPERCASE => 1,
PasswordDefinition::REQUIRE_LOWERCASE => 1,
PasswordDefinition::REQUIRE_SYMBOLS => 1,
PasswordDefinition::REQUIRE_NUMBERS => 1,
]);

// Create user with password validation
try {
$user = new UserModel();
$user->withPasswordDefinition($passwordDefinition);

$user->setName('John Doe');
$user->setEmail('[email protected]');
$user->setUsername('johndoe');
$user->setPassword($_POST['password']); // Validated automatically

$users->save($user);
echo "User created successfully";

} catch (InvalidArgumentException $e) {
echo "Password validation failed: " . $e->getMessage();
}

User-Friendly Error Messages​

<?php
function getPasswordErrors(int $result): array
{
$errors = [];

if ($result & PasswordDefinition::FAIL_MINIMUM_CHARS) {
$errors[] = "Password must be at least 8 characters long";
}
if ($result & PasswordDefinition::FAIL_UPPERCASE) {
$errors[] = "Password must contain at least one uppercase letter";
}
if ($result & PasswordDefinition::FAIL_LOWERCASE) {
$errors[] = "Password must contain at least one lowercase letter";
}
if ($result & PasswordDefinition::FAIL_NUMBERS) {
$errors[] = "Password must contain at least one number";
}
if ($result & PasswordDefinition::FAIL_SYMBOLS) {
$errors[] = "Password must contain at least one symbol (!@#$%^&*()...)";
}
if ($result & PasswordDefinition::FAIL_WHITESPACE) {
$errors[] = "Password cannot contain whitespace";
}
if ($result & PasswordDefinition::FAIL_SEQUENTIAL) {
$errors[] = "Password cannot contain sequential characters (abc, 123, etc.)";
}
if ($result & PasswordDefinition::FAIL_REPEATED) {
$errors[] = "Password cannot contain repeated patterns";
}

return $errors;
}

// Usage
$result = $passwordDefinition->matchPassword($_POST['password']);
if ($result !== PasswordDefinition::SUCCESS) {
$errors = getPasswordErrors($result);
foreach ($errors as $error) {
echo "- " . $error . "\n";
}
}

Sequential and Repeated Patterns​

Sequential Characters​

Sequential patterns that are detected include:

  • Alphabetic: abc, bcd, cde, xyz, etc. (case-insensitive)
  • Numeric: 012, 123, 234, 789, 890, etc.
  • Reverse: 987, 876, 765, 321, etc.

Repeated Patterns​

Repeated patterns include:

  • Repeated characters: aaa, 111, etc.
  • Repeated sequences: ababab, 123123, etc.

Password Change Flow​

<?php
// Password change with validation
try {
$user = $users->getById($userId);
$user->withPasswordDefinition($passwordDefinition);

// Verify old password
$existingUser = $users->isValidUser($user->getUsername(), $_POST['old_password']);
if ($existingUser === null) {
throw new Exception("Current password is incorrect");
}

// Set new password (validated automatically)
$user->setPassword($_POST['new_password']);
$users->save($user);

echo "Password changed successfully";

} catch (InvalidArgumentException $e) {
echo "New password validation failed: " . $e->getMessage();
}

Best Practices​

  1. Balance security and usability - Don't make rules too restrictive
  2. Educate users - Provide clear error messages
  3. Use password generation - Offer to generate strong passwords
  4. Consider passphrases - Allow longer passwords with spaces if appropriate
  5. Combine with rate limiting - Prevent brute force attacks

Next Steps​