Password protected area using WP generic user

0 Replies ·

  1. Hi Justin !

    This is my first topic at Theme Hybrid ! I have been using your Members plugin for a while and love it ! I have also started testing Widgets Reloaded which is super cool !

    Now I would like to get my hands dirty and start customizing some features on my site. (Just to put you in context, I am an intermediate software developer in general but kind of a beginner in PHP and Javascript). I have setup a password protected area on my site (same password for all pages, posts, CPT, events and archives of the protected area). I am using the Global Post Password plugin by John Blackbourn as it allow me to use multiple passwords. The reason I need multiple passwords is because the protected area password changes every 3 months and, when updating the password, I prefer to maintain the previous password valid for a certain period of time to allow all members to receive the new password via their monthly newsletter.

    I would like to add an extra field in the WP password form to mimic a WP user login (username and password). The username would not change over time and the password is the same for all members. I was able to add the field to the form using the_password_form filter :

    add_filter( 'the_password_form', 'custom_password_form' );
    function custom_password_form() {
    global $post;
    $o = '<form action="' . esc_url( site_url( 'wp-login.php?action=postpass', 'login_post' ) ) . '" method="post">
    ' . __( "This post is password protected. To view it please enter your username and password below: " ) . '
    <label for="Username">' . __( "Username:" ) . ' </label>
    <label for="password">' . __( "Password:" ) . ' </label>
    
    </form>
    ';
    return $o;
    }
    

    Now, I can’t figure out where to add the fake username verification. Should I hook into the Global Posts Password code ? On the other hand, I found this “pluggable” function in the WP Codex :

    function wp_check_password($password, $hash, $user_id = '') {
        global $wp_hasher;
    
        // If the hash is still md5...
        if ( strlen($hash) <= 32 ) {
            $check = hash_equals( $hash, md5( $password ) );
            if ( $check && $user_id ) {
                // Rehash using new hash.
                wp_set_password($password, $user_id);
                $hash = wp_hash_password($password);'
    

    Does that mean I can simply change the last argument, i.e. $user_id=&#039;username&#039;? But then, it seems there is no action hook I can play with…

    Option 2 would be to simply create a generic WP user as Wordpress allow infinite concurrent logins. That way, I would not need the Global Posts Password plugin anymore (which is a good thing as this plugin is not supported anymore) as it would be easy to control access to the protected area content using the Members plugin. But then, I would still have to find a way to add a second password (probably just adding a user meta field) to the generic WP user and hook into the authenticate functions (username and email) to add some custom verifications.

    Your advice (and maybe a little help and / or guidelines) would be greatly appreciated as I've been struggling around with this problem for awhile now.

    Best regards.

    (Sorry if my english is not accurate as I am a french speaking Canadian).

  2. Justin Tadlock

    Welcome to the site, Patrick.

    To post code, you can put it between backtick characters. The backtick is just below Esc on most keyboards.

    I think the easiest and most straightforward option is to create a generic user, perhaps with a custom role that has no capabilities. Then, use the Members “content permissions” feature to protect the page.

    Members has a built-in [members_login_form] shortcode, which you could use in the content permissions error message.

    The one area that we’d need to figure out is the multiple passwords. Are you saying that you have 1 password that’s currently valid for everything? That password changes every 3 months to something new? However, you want the previous password to be valid for X amount of time?

    If that’s the case, it should be possible. I’m about 95% certain I could do that with a plain text password stored as post meta (insecure but might not matter if the user account has no caps). I’d need to look into how WP hashes passwords to see if we could store it hashed (I’ve never done anything like that).

  3. Patrick Sergerie

    Thanks Justin for your super fast reply !

    Sorry for the ‘backtick’ misunderstanding. Here is a self test to make sure I am using the right character for futur topics (should I put each line of code betweek backtick characters ?) :

    add_action( 'wp_authenticate' , 'check_custom_authentication' );
    

    I am glad you have come up to the same conclusion (option 2), i.e. using a generic WP user to create a password protected area. As for the no-cap custom role, I thought we may have to give at least a read cap as the user may not see anything once logged in. And yes, I am already using the login shortcode, even the widget shortcode (which is also cool by the way !).

    As for the passwords requirement, your understanding is right. As an example, current password should be valid from start date September 1st 2017 to end date November 30th 2017 (approximately 3 months), and previous password should be valid from (let’s say) start date June 1st 2017 to end date September 15th 2017. That would allow members that, for some reasons, have not yet received the new password to use the previous password for an additional period of 2 weeks.

    To store the additional password and possibly the validity dates for both passwords, we could indeed use post meta or possibly even add custom user profile fields. The though part, as you mentionned, would be to store a second hashed password.

    Finally, correct me if I am wrong, but we would then have to hook to the authenticate filters to add new conditions :

    add_filter( 'authenticate', 'wp_authenticate_username_password',  20, 3 );
    add_filter( 'authenticate', 'wp_authenticate_email_password',     20, 3 );
    

    If I am in the right direction, the next step will be to put everything together, which may be a challenge for me !

  4. Justin Tadlock

    This is probably going to be the starting point for a hashed password: https://codex.wordpress.org/Function_Reference/wp_hash_password

    The authenticate hook looks like the right hook: https://codex.wordpress.org/Plugin_API/Filter_Reference/authenticate

    If you want to wait to see if WP authenticates first, you’ll want to hook in with a later priority than 20.

    For the custom users, you do not want to give them the read cap. Otherwise, anyone could log in and change the password for the account in the admin. You may still need to do some things regarding resetting the password too, but we can look at that later.

    So, the first step is going to be setting the extra password for via user meta. If you want a field in the admin for this, you’ll need to do something along the lines of the following tutorial (note: use update_user_meta() instead of update_usermeta() – older tutorial): http://justintadlock.com/archives/2009/09/10/adding-and-using-custom-user-profile-fields

    When saving the password there, you’ll want to hash it like so:

    $hash = wp_hash_password( $_POST['your_form_field_name'] );
    
    update_user_meta( $user_id, 'backup_password', true );
    

    Then, you’ll need to hook into authenticate to check the backup password. Here’s an extremely rough outline of what I have in mind.

    add_filter( 'authenticate', 'th_authenticate', 25, 3 );
    
    function th_authenticate( $user, $username, $pass ) {
    
        // Already authenticated, bail.
        if ( $user instanceof WP_User ) {
            return $user;
        }
    
        if ( is_wp_error( $user ) ) {
    
            $codes = $user->get_error_codes();
    
            // If already had an invalid email or username, bail.
            if ( in_array( 'invalid_email', $codes ) || in_array( 'invalid_username', $codes ) ) {
                return $user;
            }
        }
    
        if ( $_POST['pwd'] ) {
    
            $_user = get_user_by( 'login', $username );
    
            if ( ! $_user )
                $_user = get_user_by( 'email', $username );
    
            if ( $_user ) {
    
                $hash = get_user_meta( $_user->ID, 'backup_password', true );
    
                if ( wp_check_password( $_POST['pwd'], $hash ) )
                    return $_user;
            }
        }
    
        return $user;
    }
    

    I do want to stress that I’m not 100% sure of what I’m doing at this point. I’m just looking through the core code to see what I can piece together.

  5. Patrick Sergerie

    That’s more than enough material to start playing with ! Thank you Justin ! I will try to work on this in the next few days (it’s a part time job for me, and I still have much to learn !) and I will get back to you with results…

    Have a great week-end !