2

I'm currently trying to build a custom reset password form, which seems to work fine up until it needs to validate the key for the user:

//Redirect away from default wordpress to reset password
function redirect_to_reset_password() {
    if ( 'GET' == $_SERVER['REQUEST_METHOD'] ) {
        // Verify key / login combo
        $user = check_password_reset_key( $_REQUEST['key'], $_REQUEST['login'] );
        if ( ! $user || is_wp_error( $user ) ) {
            if ( $user && $user->get_error_code() === 'expired_key' ) {
                wp_redirect( home_url( '/login?login=expiredkey/' ) );
            } else {
                wp_redirect( home_url( '/login?login=invalidkey/' ) );
            }
            exit;
        }

        $redirect_url = home_url( '/reset-password/' );
        $redirect_url = add_query_arg( 'login', esc_attr( $_REQUEST['login'] ), $redirect_url );
        $redirect_url = add_query_arg( 'key', esc_attr( $_REQUEST['key'] ), $redirect_url );

        wp_redirect( $redirect_url );
        exit;
    }
}
add_action('login_form_rp', 'redirect_to_reset_password');
add_action('login_form_resetpass', 'redirect_to_reset_password');

//Make new password
function do_password_reset() {
    if ( 'POST' == $_SERVER['REQUEST_METHOD'] ) {
        $rp_key = $_REQUEST['rp_key'];
        $rp_login = $_REQUEST['rp_login'];
        $user = check_password_reset_key( $rp_key, $rp_login );

        if ( ! $user || is_wp_error( $user ) ) {
            if ( $user && $user->get_error_code() === 'expired_key' ) {
                wp_redirect( home_url( '/login?login=expiredkey/' ) );
            } else {
                wp_redirect( home_url( '/login?login=invalidkey/' ) );
            }
            exit;
        }

        if ( isset( $_POST['pass1'] ) ) {
            if ( $_POST['pass1'] != $_POST['pass2'] ) {
                // Passwords don't match
                $redirect_url = home_url( '/reset-password/' );

                $redirect_url = add_query_arg( 'key', $rp_key, $redirect_url );
                $redirect_url = add_query_arg( 'login', $rp_login, $redirect_url );
                $redirect_url = add_query_arg( 'error', 'password_reset_mismatch', $redirect_url );

                wp_redirect( $redirect_url );
                exit;
            }

            if ( empty( $_POST['pass1'] ) ) {
                // Password is empty
                $redirect_url = home_url( '/reset-password/' );

                $redirect_url = add_query_arg( 'key', $rp_key, $redirect_url );
                $redirect_url = add_query_arg( 'login', $rp_login, $redirect_url );
                $redirect_url = add_query_arg( 'error', 'password_reset_empty', $redirect_url );

                wp_redirect( $redirect_url );
                exit;
            }

            // Parameter checks OK, reset password
            reset_password( $user, $_POST['pass1'] );
            wp_redirect( home_url( '/login?password=changed/' ) );
        } else {
            echo "Invalid request.";
        }

        exit;
    }
}
add_action( 'login_form_rp', 'do_password_reset' );
add_action( 'login_form_resetpass', 'do_password_reset' );

I've been snooping around trying to figure out what the cause of the issue can be, and I've found that the key under user_activation_key is not the same as the one I get in my URL when trying to reset my password. E.g:

DB: 1553346836:$P$BYEbftAGRfnhlBTeuNL4ylhsxRyhS3/

URL: key=dx1GjoJnaD6Dytc5zpNq

I assume this is just wordpress' way of encrypting the key so it shouldn't cause this issue, but it is the only inconsistency I've been able to find.

EDIT: If I try and check what value $user is set to, I get an undefined index error regarding $_REQUEST['rp_key'] and $_REQUEST['rp_login']. I then tried to var_dump $_REQUEST which gave me the following: array(2) { ["login"]=> string(20) "[email protected]" ["key"]=> string(20) "nWrRCsRD4OvaROeMFErV" }. Following this I changed $_REQUEST['rp_key'] to $_REQUEST['key'] and the same for login, but I still get the same undefined index error. But the value is in the $_REQUEST array, why can't it find it?

Could the fact that I'm using check_password_reset_key twice have an effect on the key being set to invalid?

EDIT2: Running the code through a environment running https resulted in a "Bad Request" return.

7
  • So you're saying that check_password_reset_key always gives you invalid_key ? Yes, the DB value is the timestamp you requested the reset (which is four days old now: resets are only valid for 24 hours by default) and a phpass hash of the key.
    – Rup
    Mar 27, 2019 at 16:13
  • That hashed key is correct. From wp-includes, php -a require_once("class-phpass.php"); echo ((new PasswordHash(8,true))->CheckPassword('dx1GjoJnaD6Dytc5zpNq', '$P$BYEbftAGRfnhlBTeuNL4ylhsxRyhS3/')) ? "true" : "false"; gives me "true".
    – Rup
    Mar 27, 2019 at 16:15
  • Have you checked, isn't there a way to re-render or re-use WordPress’ built-in password reset form/code?
    – leymannx
    Mar 27, 2019 at 17:37
  • @Rup so am I correct in assuming the error is either how I use check_password_reset_key or how I request the key and login?
    – ok34332
    Mar 28, 2019 at 14:38
  • I don't know, sorry: at first glance it looks correct to me. If those are the exact values you're feeding into check_password_reset_key then it ought to work. Assuming you're not hitting the reset-key-expired error that is, but that returns 'expired_key'.
    – Rup
    Mar 28, 2019 at 14:40

2 Answers 2

2

I had the same issue with this, I assume you're working from this guide - https://code.tutsplus.com/tutorials/build-a-custom-wordpress-user-flow-part-3-password-reset--cms-23811

Have a look at the actual reset password form where it shows pass1 and pass2. The hidden fields values are automatically set to $attribute['key'] and $attribute['login'].

Changing this to $_REQUEST['key'] and $_REQUEST['login'] should solve the issue.

Hope this helps.

0

I think the problem here is the fact that you're using $_REQUEST instead of explicitly looking for GET or POST variables.

I think you already solved the first issue which is that the first step of the process (where you request a reset) passes key and login via $_GET variables. This allows redirect_to_reset_password() to redirect appropriately.

The next part is a bit different. The reset password form passes the following:

Array (
    [action] => resetpass
    [pass1] => [text]
    [pass1-text] => [text]
    [pass2] => [text]
    [rp_key] => [text]
    [wp-submit] => Reset Password
)

Unless you're doing something to change these fields on a form from your /reset-password/ page, rp_login will never be set. wp-login.php shows a different process to retrieving the necessary pieces to run check_password_reset_key()

if ( isset( $_COOKIE[ $rp_cookie ] ) && 0 < strpos( $_COOKIE[ $rp_cookie ], ':' ) ) {
        list( $rp_login, $rp_key ) = explode( ':', wp_unslash( $_COOKIE[ $rp_cookie ] ), 2 );
        $user                      = check_password_reset_key( $rp_key, $rp_login );
        if ( isset( $_POST['pass1'] ) && ! hash_equals( $rp_key, $_POST['rp_key'] ) ) {
            $user = false;
        }
}

You can see it's checking the rp_cookie that was set earlier in the process. From here it's retrieving the login and key then checking the cookie's key against the key passed via a POST variable.

You will need to do something similar to make this work.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.