Support » Developing with WordPress » WP_REST_API Nonce not working.

  • So i am trying to Add authorization to my custom rest endpoint

    here is some code:

    
    		add_action('rest_api_init', function() {
      		register_rest_route('posts_bank/v1', '/import_posts/(?P<id>\d+)', array(
        		'methods' => 'POST',
        		'callback' => array(&$this , 'import_posts'),
    			));
    		});
        wp_register_script('posts-bank-main', plugins_url( '/templates/js/main.js', __FILE__ ));
    		wp_register_style('posts-bank-main', plugins_url( '/templates/css/main.css', __FILE__ ));
    
    		wp_localize_script('posts-bank-main', 'wpApiSettings', array(
    			'nonce' => wp_create_nonce('import_posts')
    		));
    	  wp_enqueue_script('admin-widgets');
    	  wp_enqueue_script('posts-bank-main');
    		wp_enqueue_style('posts-bank-main');
    
    

    And heres the JS:

    		var url = "http://localhost/?rest_route=/posts_bank/v1/import_posts/1";
    		$.ajax({
          url: url,
    			method: "POST",
    			beforeSend: function ( xhr ) {
            xhr.setRequestHeader( 'X-WP-Nonce', wpApiSettings.nonce );
        	},
    			data: {"_nonce": wpApiSettings.nonce},
          success: (response) => {
    				alert(1);
          }
        });

    But wordpress responds with:
    {code: "rest_cookie_invalid_nonce", message: "Cookie nonce is invalid", data: {status: 403}}

    How to resolve this?

    Thanks!

Viewing 3 replies - 1 through 3 (of 3 total)
  • Hi @ivrrimum,

    Bumped into the same issue today, turns out it’s not an issue at all but a misunderstanding of how that part actually works.

    Basically what’s not working is that you’re replacing the default REST API nonce with your own, making the check fail because WordPress has no knowledge of you new nonce action and therefore can’t verify it properly; If you change your ‘import_posts’ action to ‘wp_rest’, the default nonce action, it works as expected. If you want to use your custom action you have to hook to the rest_authentication_errors filter and perform your own checks:

    
    add_filter( 'rest_authentication_errors', function( $result ) {
    
    	// $result should be null. Any other value indicates something
    	// happened before we got here.
    	if ( ! is_null( $result ) || is_wp_error( $result ) ) {
    		return $result;
    	}
    
    	// Make sure we need to authenticate at all.
    	if ( empty( $_REQUEST['wtfaid'] ) || empty( $_REQUEST['_nonce'] ) ) {
    		return $result;
    	}
    
    	// Verifiy the nonce.
    	$nonce = $_REQUEST['_nonce'];
    	if ( wp_verify_nonce( $nonce, 'import_posts' ) ) {
    		return true;
    	}
    
    	// If we're still here, just return the original result.
    	return $result;
    });
    

    Note that I added a wtfaid parameter to the Ajax request data to trigger the nonce verification only when I want it and not on every single API request :

    
    var url = "https://wp-latest.d3v.talyes.pro/?rest_route=/posts_bank/v1/import_posts/1";
    $.ajax({
    	url: url,
    	method: "POST",
    	beforeSend: function ( xhr ) {
    		xhr.setRequestHeader( 'X-WP-Nonce', wpApiSettings.nonce );
    	},
    	data: {
    		_nonce : wpApiSettings.nonce,
    		wtfaid : 'hello'
    	},
    	success: (response) => {
    		alert(1);
    	}
    });
    

    Also note that this method could have some collateral damage because of the replacing of the default action nonce if some other plugins/themes/features rely on it. I don’t know in what context you’re using this, but have you considered using the Backbone client for the REST API? If you’re dealing with custom post types and stuff it’s a really useful way to make use of the REST API, including permission checks, post/meta/terms creation/update/delete in a few lines of JS.

    Anyway, hope that helped you somehow!

    Moderator bcworkz

    (@bcworkz)

    Also note that this method could have some collateral damage because of the replacing of the default action nonce if some other plugins/themes/features rely on it.

    A reasonable concern, but when handled properly it will be fine. The key is to pass on whatever your callback received unless the request applies to your specific app and your callback received null as the passed parameter. If anything else besides null is returned, it will be treated as the final authentication determination. In Charlie’s case, confirming his ‘wtfaid’ data ensures the request applies only to his app.

    Your filter hook must be added with a priority < 100 (if not specified, it is 10 by default). The WP core nonce check (rest_cookie_check_errors()) is added with priority 100. It assumes it is the final auth arbitrator, anything later will never be passed null.

    Always keep in mind your callback here is a complete auth check, not merely a custom nonce action check. The source code shown in the above link is a good example of proper auth handling, except it does not check if the request is specific to your app, since it applies to all cookie authentications using the default ‘wp_rest’ nonce action.

    If your app is also using the default cookie auth method, but using a custom nonce action, your auth callback must also verify the cookie and that the user is logged in just like rest_cookie_check_errors() does. Failure to do so introduces a security vulnerability. ( I’m looking at you, Charlie 🙂 ) WP still does the usual capability checks for the current user when doing anything, so the vulnerability is not as bad as it may sound, but it’s a weakness never the less.

    If your app is also using the default cookie auth method, but using a custom nonce action, your auth callback must also verify the cookie and that the user is logged in just like rest_cookie_check_errors() does. Failure to do so introduces a security vulnerability. (I’m looking at you, Charlie)

    Absolutely. That was implied by my “perform your own checks”, but I reckon it could have been more explicit.

Viewing 3 replies - 1 through 3 (of 3 total)
  • The topic ‘WP_REST_API Nonce not working.’ is closed to new replies.