Changing Password via AJAX with the WordPress REST API Changing Password via AJAX with the WordPress REST API wordpress wordpress

Changing Password via AJAX with the WordPress REST API


It seems that some $_COOKIE or $_SESSION variables that WordPress relies on to generate nonces are not being updated until the page refresh.

Yes, and it is LOGGED_IN_COOKIE, which defaults to:

'wordpress_logged_in_' . COOKIEHASH

Take a look at:

So you'd replace this code: (and I wouldn't use wp_generate_auth_cookie() for this purpose; and instead, use what's already been generated via wp_set_auth_cookie())

//Set the cookie immediately$_COOKIE[AUTH_COOKIE] = wp_generate_auth_cookie($userID, 2 * DAY_IN_SECONDS);

..with this one:

$_set_cookies = true; // for the closures// Set the (secure) auth cookie immediately. We need only the first and last// arguments; hence I renamed the other three, namely `$a`, `$b`, and `$c`.add_action( 'set_auth_cookie', function( $auth_cookie, $a, $b, $c, $scheme ) use( $_set_cookies ){    if ( $_set_cookies ) {        $_COOKIE[ 'secure_auth' === $scheme ? SECURE_AUTH_COOKIE : AUTH_COOKIE ] = $auth_cookie;    }}, 10, 5 );// Set the logged-in cookie immediately. `wp_create_nonce()` relies upon this// cookie; hence, we must also set it.add_action( 'set_logged_in_cookie', function( $logged_in_cookie ) use( $_set_cookies ){    if ( $_set_cookies ) {        $_COOKIE[ LOGGED_IN_COOKIE ] = $logged_in_cookie;    }} );// Set cookies.wp_set_auth_cookie($userID);$_set_cookies = false;

Working Example (tested on WordPress 4.9.5)

PHP / WP REST API

function myplugin__change_password( $password, $userID ) {    //$userID = get_current_user_id();    wp_set_password($password, $userID);    // Log user in.    wp_set_current_user($userID);    $_set_cookies = true; // for the closures    // Set the (secure) auth cookie immediately. We need only the first and last    // arguments; hence I renamed the other three, namely `$a`, `$b`, and `$c`.    add_action( 'set_auth_cookie', function( $auth_cookie, $a, $b, $c, $scheme ) use( $_set_cookies ){        if ( $_set_cookies ) {            $_COOKIE[ 'secure_auth' === $scheme ? SECURE_AUTH_COOKIE : AUTH_COOKIE ] = $auth_cookie;        }    }, 10, 5 );    // Set the logged-in cookie immediately. `wp_create_nonce()` relies upon this    // cookie; hence, we must also set it.    add_action( 'set_logged_in_cookie', function( $logged_in_cookie ) use( $_set_cookies ){        if ( $_set_cookies ) {            $_COOKIE[ LOGGED_IN_COOKIE ] = $logged_in_cookie;        }    } );    // Set cookies.    wp_set_auth_cookie($userID);    $_set_cookies = false;    //Return fresh nonce    return new WP_Rest_Response(array(        'nonce'  => wp_create_nonce('wp_rest'),        'status' => 'password_changed',    ));}function myplugin_change_password( WP_REST_Request $request ) {    $old_pwd = $request->get_param( 'old_pwd' );    $new_pwd = $request->get_param( 'new_pwd' );    $user = wp_get_current_user();    if ( ! wp_check_password( $old_pwd, $user->user_pass, $user->ID ) ) {        return new WP_Error( 'wrong_password', 'Old password incorrect' );    }    if ( $old_pwd !== $new_pwd ) {        return myplugin__change_password( $new_pwd, $user->ID );    }    return new WP_Rest_Response( [        'nonce'  => wp_create_nonce( 'wp_rest' ),        'status' => 'passwords_equal',    ], 200 );}add_action( 'rest_api_init', function(){    register_rest_route( 'myplugin/v1', '/change-password', [        'methods'             => 'POST',        'callback'            => 'myplugin_change_password',        'args'                => [            'old_pwd' => [                'type'              => 'string',                'validate_callback' => function( $param ) {                    return ! empty( $param );                }            ],            'new_pwd' => [                'type'              => 'string',                'validate_callback' => function( $param ) {                    return ! empty( $param );                }            ],        ],        'permission_callback' => function(){            // Only logged-in users.            return current_user_can( 'read' );        },    ] );} );

HTML / The form

<fieldset>    <legend>Change Password</legend>    <p>        To change your password, please enter your old or current password.    </p>    <label>        Old Password:        <input id="old_passwd">    </label>    <label>        New Password:        <input id="new_passwd">    </label>    <label>        Old nonce: (read-only)        <input id="old_nonce" value="<?= wp_create_nonce( 'wp_rest' ) ?>"        readonly disabled>    </label>    <label>        New nonce: (read-only)        <input id="new_nonce" readonly disabled>    </label>    <button id="go" onclick="change_passwd()">Change</button></fieldset><div id="rest-res"><!-- AJAX response goes here --></div>

jQuery / AJAX

function change_passwd() {    var apiurl = '/wp-json/myplugin/v1/change-password',        $ = jQuery;    $.post( apiurl, {        old_pwd: $( '#old_passwd' ).val(),        new_pwd: $( '#new_passwd' ).val(),        _wpnonce: $( '#new_nonce' ).val() || $( '#old_nonce' ).val()    }, function( res ){        $( '#new_nonce' ).val( res.nonce );        // Update the global nonce for scripts using the `wp-api` script.        if ( 'object' === typeof wpApiSettings ) {            wpApiSettings.nonce = res.nonce;        }        $( '#rest-res' ).html( '<b>Password changed successfully.</b>' );    }, 'json' ).fail( function( xhr ){        try {            var res = JSON.parse( xhr.responseText );        } catch ( err ) {            return;        }        if ( res.code ) {            $( '#rest-res' ).html( '<b>[ERROR]</b> ' + res.message );        }    });}


You need to pass the nonce through the X-WP-Nonce header with your AJAX request. Something like:

beforeSend: function ( xhr ) {    xhr.setRequestHeader( 'X-WP-Nonce', localizedScript.nonce );}

Where localizedScript is the script your nonce is localized to. See the codex for more info on localization. Note the localizedScript method is NOT required.

Without a localizedSript your AJAX could look similar to the following:

var _nonce = "<?php echo wp_create_nonce( 'wp_rest' ); ?>";$.ajax({    type: 'POST',    url: url_path,    data: {},    dataType: 'json',    beforeSend: function ( xhr ) {        xhr.setRequestHeader( 'X-WP-Nonce', _nonce );    }});

Obviously copying that exact script above wont get it done for you, but that's essentially how you need to pass the nonce successfully.