How to enforce strong passwords with WordPress filters in WordPress

Contents

Overview

This article explains, in full detail, how to enforce strong passwords in WordPress using server-side filters and actions. Client-side password meters (JavaScript) help users choose better passwords, but they can be bypassed enforcing rules on the server is the only reliable way to make sure every account meets your password policy. Well cover every important hook and scenario (registration, profile updates, password resets, multisite signups, programmatic user creation) and provide ready-to-drop-in PHP examples with progressively stricter rules, entropy estimation, role-based exceptions, and install instructions.

Why server-side enforcement?

  • Impossible to bypass if applied to all entry points (registration form, profile update, password reset, multisite signup, custom APIs).
  • Protects against clients with JavaScript disabled and programmatic attempts to create weak accounts through custom code or API calls.
  • Provides consistent error messages and logging for administrative auditing.

Where to put this code

  1. Preferred: a must-use plugin (wp-content/mu-plugins/). This ensures enforcement cannot be accidentally disabled by deactivating a theme or plugin.
  2. Alternative: a small standalone plugin in wp-content/plugins/ and network-activate when on multisite, or add to a sites theme functions.php (less recommended).

Which WordPress hooks to use

To cover all UI flows use:

  • registration_errors (filter) — validates the standard wp-login.php registration flow.
  • user_profile_update_errors (action) — fires for profile updates in admin and front-end profile forms (used by wp-admin profile edit).
  • validate_password_reset (action) — used when a user completes reset via the reset form add errors to block weak reset passwords.
  • wpmu_validate_user_signup (filter) — for multisite signups.

Design considerations and rules to enforce

Common rules you may want to enforce (choose which to require):

  • Minimum length (recommended: 12 characters).
  • Character class requirements: lowercase, uppercase, digits, symbols.
  • No inclusion of username or email local-part.
  • Not on a blacklist of commonly used weak passwords.
  • Minimum entropy (computed server-side as an estimate).
  • Role-based or capability-based exemptions (e.g., allow automation accounts to use strong API keys instead of passwords).

Basic enforcement (minimum length simple feedback)

This simple code enforces a minimum length for registration, profile updates, and password resets. Drop it in a must-use plugin file or your sites plugin.

add( weak_password, __( Error: Password must be at least 12 characters long., wpesr ) )
    }
    return errors
}

/ Profile update (admin / front-end) /
add_action( user_profile_update_errors, wpesr_profile_check, 10, 3 )
function wpesr_profile_check( errors, update, user ) {
    // When editing profile, the new password is often in pass1.
    if ( ! empty( _POST[pass1] ) ) {
        password = wpesr_get_post_password()
        if ( ! wpesr_is_strong_basic( password ) ) {
            errors->add( weak_password, __( Error: Password must be at least 12 characters long., wpesr ) )
        }
    }
}

/ Password reset /
add_action( validate_password_reset, wpesr_validate_reset, 10, 2 )
function wpesr_validate_reset( errors, user ) {
    if ( empty( _POST[pass1] ) ) {
        return
    }
    password = wpesr_get_post_password()
    if ( ! wpesr_is_strong_basic( password ) ) {
        errors->add( weak_password, __( Error: Password must be at least 12 characters long., wpesr ) )
    }
}
?>

Notes

  • This snippet reads password fields from _POST — there is no universal form field name across all plugins or endpoints, so you may need to adapt if a custom form uses a different field name.
  • The filter registration_errors must return the errors object the user_profile_update_errors and validate_password_reset actions receive the same WP_Error object for adding errors.

Advanced enforcement: complexity, blacklist, and entropy

The next example provides:

  • Minimum length and character class checks
  • Blacklist lookup
  • Username/email-embedded password check
  • Estimated entropy calculation (bits) using character pool heuristics
  • Role-based exemptions and clear, translatable error messages
add( weak_password, __( Error: Your chosen password does not meet the sites security requirements., wpesa ) )

    // Optionally attach each reason as a separate error code for admins or debug.
    foreach ( reasons as i => reason ) {
        errors_obj->add( weak_password_detail_{i}, reason )
    }
}

/ Registration /
add_filter( registration_errors, wpesa_registration_check, 10, 3 )
function wpesa_registration_check( errors, sanitized_user_login, user_email ) {
    password = wpesa_get_post_password()
    if ( password ===  ) {
        return errors
    }

    // Optionally, allow certain automated processes by checking capability/context here.

    reasons = wpesa_password_weaknesses( password, sanitized_user_login, user_email )
    wpesa_add_errors_from_reasons( errors, reasons )
    return errors
}

/ Profile update /
add_action( user_profile_update_errors, wpesa_profile_check, 10, 3 )
function wpesa_profile_check( errors, update, user ) {
    // Admins can edit others profiles use user->ID and current_user_can checks to tailor behavior.
    if ( empty( _POST[pass1] ) ) {
        return
    }
    // Optional: bypass for certain roles (example: allow administrators to use different process)
    if ( isset( user->roles )  is_array( user->roles )  in_array( some_exempt_role, user->roles, true ) ) {
        return
    }

    password = wpesa_get_post_password()
    reasons = wpesa_password_weaknesses( password, isset( user->user_login ) ? user->user_login : , isset( user->user_email ) ? user->user_email :  )
    wpesa_add_errors_from_reasons( errors, reasons )
}

/ Password reset /
add_action( validate_password_reset, wpesa_validate_reset, 10, 2 )
function wpesa_validate_reset( errors, user ) {
    if ( empty( _POST[pass1] ) ) {
        return
    }
    password = wpesa_get_post_password()
    reasons = wpesa_password_weaknesses( password, isset( user->user_login ) ? user->user_login : , isset( user->user_email ) ? user->user_email :  )
    wpesa_add_errors_from_reasons( errors, reasons )
}

/ Multisite signup /
if ( is_multisite() ) {
    add_filter( wpmu_validate_user_signup, wpesa_wpmu_validate_user_signup )
    function wpesa_wpmu_validate_user_signup( result ) {
        // result is an array with keys: user_name, user_email, errors
        password = wpesa_get_post_password()
        if ( password ===  ) {
            return result
        }
        reasons = wpesa_password_weaknesses( password, result[user_name], result[user_email] )
        if ( ! empty( reasons ) ) {
            // wpmu_validate_user_signup uses WP_Error style array of errors in result[errors]
            if ( isset( result[errors] )  is_object( result[errors] ) ) {
                foreach ( reasons as i => r ) {
                    result[errors]->add( weak_password_{i}, r )
                }
            }
        }
        return result
    }
}

/ Optional: wrapper for programmatic user creation /
function wpesa_create_user_strict( username, password, email =  ) {
    reasons = wpesa_password_weaknesses( password, username, email )
    if ( ! empty( reasons ) ) {
        // Return WP_Error so callers can handle failure.
        return new WP_Error( weak_password, __( Password does not meet requirements., wpesa ), reasons )
    }
    return wp_create_user( username, password, email )
}
?>

Explanation of the advanced example

  • The entropy estimator uses a simple character pool heuristic: if the password contains lowercase letters, add 26, uppercase 26, digits 10, and symbols 32. Entropy = length log2(pool). This is a conservative estimate sufficient to prevent obviously weak passwords server-side.
  • Blacklist is a minimal sample. For production, use a larger list (thousands/ten-thousands) or integrate with public banned-password lists.
  • We do not log the password itself anywhere (never log plain passwords). If you add logging, only include non-sensitive metadata or obfuscated details.
  • The code adds both a generic weak_password error and additional detailed error entries. The generic message avoids exposing implementation details to attackers, while the detailed errors can help legitimate users pick a stronger password. You can choose to present or hide the detailed messages depending on UX decisions.

Handling programmatic user creation and custom endpoints

Some code paths bypass the standard UI hooks:

  • Direct calls to wp_insert_user or wp_create_user in custom plugins or scripts.
  • Custom REST API endpoints that create users.

To make sure your policy applies to these, either:

  1. Call your validation function (like wpesa_password_weaknesses) before programmatic calls, or
  2. Wrap or replace wp_create_user with your own wrapper, or
  3. Use a unit-test or code-review process to validate codepaths create accounts only through approved helpers.
get_error_messages() or result->get_error_data()
} else {
    // Created — result is the new user ID.
}
?>

Multisite-specific considerations

  • Use the wpmu_validate_user_signup filter to block weak passwords during network signups (shown in the advanced example).
  • Network admins can still create users via the network admin screens. Ensure the same hooks run there (user_profile_update_errors and validate_password_reset fire appropriately).

Testing checklist

  1. Try registration with a password that violates each rule and verify that the flow returns the expected error message.
  2. Test user profile updates from wp-admin and, if you have a front-end profile form, test that flow as well.
  3. Complete a password reset using an email link and attempt to set a weak password confirm enforcement.
  4. If you have custom code that creates users, test and ensure the wrapper or checks are in place.
  5. Test multisite signup if applicable.

Troubleshooting

  • If changes do not take effect, confirm the plugin file is placed in wp-content/mu-plugins or activated and PHP is not throwing a fatal error. Enable WP_DEBUG to show errors during development.
  • If a third-party plugin also enforces password validation, ensure the priority of your filter/action is appropriate (higher or lower priority depending on which should run first) and coordinate behavior with that plugin.
  • Forms that use different POST field names require adapting the password extraction helper to match the actual field name.

Security and UX recommendations

  • Do not reveal too much detail to potential attackers. Present a single high-level error to unauthenticated users, and show detailed guidance to logged-in users or inside the UI only.
  • Encourage use of password managers and passphrases. A 16-character passphrase consisting of multiple words often beats a short complex password.
  • Use HTTPS to protect passwords in transit.
  • Do not log passwords or store them in any persistent unencrypted form. Always use WordPress functions to set passwords (wp_set_password, wp_insert_user) which store hashed passwords using WPs hashing functions.

Internationalization and messages

Wrap user-facing strings in translation functions like __() and _e() with a text domain so they can be localized. The examples above use basic translation calls for that reason.

Example: full plugin file (recommended as mu-plugin)

Save the advanced example as a file named like wp-enforce-strong-passwords.php in wp-content/mu-plugins/ (create the folder if it doesnt exist). Because its a mu-plugin it will load on every site and cannot be deactivated through the admin UI, which is typically desired for password policy enforcement.

Final notes

Enforcing strong passwords at the server level is essential for account security. The code patterns shown above give you a complete, practical toolkit:

  • Use registration_errors, user_profile_update_errors and validate_password_reset to enforce rules for user-facing forms.
  • Extend rules with blacklist checks, username/email checks, and entropy estimation for stronger guarantees.
  • Apply the same checks to programmatic user creation, or provide wrappers that validate before creating accounts.
  • Consider UX: balance strictness with user guidance provide clear instructions on how to choose a compliant password.

Use the provided examples as a base and customize length, entropy threshold, blacklist, or special-case exemptions to match your organizations security policy.



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Leave a Reply

Your email address will not be published. Required fields are marked *