1

I'm setting up multiple custom api routes in my wordpress theme and I would like that to require a JWT token to access.

So I have followed the steps in order to get the JWT Authentication for WP Rest API plugin and I am able to successfully get a token and use it on default routes.

However, on custom routes it is not needed and if no Authorization header is specified I successfully get the data from route. If I provide an Authorization with a bad bearer token then the JWT plugin will kick in and return bad token but this means that as long as no Authorization header is defined, the route can be used freely.

Here are my custom routes:

add_action('rest_api_init', function() {
    register_rest_route('mysite/v1', '/ads/', {
        [ 
            'methods'  => 'GET',
            'callback' => 'get_ads',
        ],
        [
            'methods'    => 'POST',
            'callback'   => 'create_ad',
        ],
    ]);
});

function create_ad() {
    global $wpdb;

    $result = $wpdb->query(...);

    return $result;
}

function get_ads() {
    global $wpdb;

    $ads = $wpdb->get_results('SELECT * from `ads`');

    return $ads;
}
1

2 Answers 2

2

Late, but maybe helpful for other readers as I added solution specifically to above code of this question.

Solution: Permission Callback function

WordPress: version 5.7.2
PHP: version 7.4
host: hostmonster.com
client: Windows 10
browsers: tested on Chrome, Firefox, even Edge 😜 worked

Code (PHP code in function.php of your installed theme):

add_action('rest_api_init', function() {
    /**
     * Register here your custom routes for your CRUD functions
     */
    register_rest_route( 'mysite/v1', '/ads/', array(
        array(
            'methods'  => WP_REST_Server::READABLE, // = 'GET'
            'callback' => 'get_ads',
            // Always allow, as an example
            'permission_callback' => '__return_true'
        ),
        array(
            'methods'  => WP_REST_Server::CREATABLE, // = 'POST'
            'callback' => 'create_ad',
            // Here we register our permissions callback
            // The callback is fired before the main callback to check if the current user can access the endpoint
            'permission_callback' => 'prefix_get_private_data_permissions_check',
        ),
    ));
});

// The missing part:
// Add your Permission Callback function here, that checks for the cookie
// You should define your own 'prefix_' name, though

function prefix_get_private_data_permissions_check() {
    // Restrict endpoint to browsers that have the wp-postpass_ cookie.

    if ( !isset($_COOKIE['wp-postpass_'. COOKIEHASH] )) {
        return new WP_Error( 'rest_forbidden', esc_html__( 'OMG you can not create or edit private data.', 'my-text-domain' ), array( 'status' => 401 ) );
    };
    return true;
};

function create_ad() {
    global $wpdb;

    $result = $wpdb->query(...);

    return $result;
}

function get_ads() {
    global $wpdb;

    $ads = $wpdb->get_results('SELECT * from `ads`');

    return $ads;
}

Make sure to include in your HTML page credentials: 'same-origin' in your HTTP request.

Code (HTML with inline <script> ... </script>):

<script>

// Here comes the REST API part:
// HTTP requests with fetch() promises

function getYourAds() {
  let url = 'https://example.com/wp-json/mysite/v1/ads/';
  fetch(url, {
    method: 'GET',
    credentials: 'same-origin', // <-- make sure to include credentials
    headers:{
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        //'Authorization': 'Bearer ' + token  <-- not needed, WP does not check for it
    }
  }).then(res => res.json())
  .then(response => get_success(response))
  .catch(error => failure(error));
};

function insertYourAds(data) {
  let url = 'https://example.com/wp-json/mysite/v1/ads/';
  fetch(url, {
    method: 'POST',
    credentials: 'same-origin', // <-- make sure to include credentials
    headers:{
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        //'Authorization': 'Bearer ' + token  <-- not needed, WP does not check for it
    },
    body: JSON.stringify(data)
  }).then(res => res.json())
  .then(response => create_success(response))
  .catch(error => failure(error));
};

// your Success and Failure-functions:

function get_success(json) {
  // do something here with your returned data ....
  console.log(json);
};

function create_success(json) {
  // do something here with your returned data ....
  console.log(json);
};

function failure(error) {
  // do something here ....
  console.log("Error: " + error);
};

</script>

Final thoughts:

Is 'Authorization': 'Bearer ' + token necessary in header of HTTP request?

After some testing, I realized that if ( !isset($_COOKIE['wp-postpass_'. COOKIEHASH] )) { ... within the Permission Callback not only checks if the Cookie is set on client browser, but it seems also to check its value (the JWT token).

Because I dobble checked as with my initial code, passing a false token, eliminating the cookie, or leaving session open but changing in the back-end the password of site (hence WordPress would create a new token, hence value of set wp_postpass_ cookie would change) and all test went correctly - REST API blocked, not only verifying presence of cookie, but also its value (which is good - thank you WordPress team).

Sources:
I found following resource concerning above thoughts in the FAQ section:

Why is the REST API not verifying the incoming Origin header? Does this expose my site to CSRF attacks?

Because the WordPress REST API does not verify the Origin header of incoming requests, public REST API endpoints may therefore be accessed from any site. This is an intentional design decision.

However, WordPress has an existing CSRF protection mechanism which uses nonces.

And according to my testing so far, the WP-way of authentication works perfectly well.

Thumbs up 👍 for the WordPress team

Additional 2 sources from the WordPress REST API Handbook:

REST API Handbook / Extending the REST API / Routes and Endpoints
REST API Handbook / Extending the REST API / Adding Custom Endpoints

For those interested in full story of my findings, following link to my thread with answers, code snippets and additional findings.

How to force Authentication on REST API for Password protected page using custom table and fetch() without Plugin

0

Controlling access to a REST endpoint should be as simple as using current_user_can() with a core/custom capability:

add_action(
    'rest_api_init',
    function() {
        register_rest_route(
            'your-organisation/your-plugin',
            '/your-endpoint/',
            [
                'show_in_index' => false,
                'methods' => 'POST',
                'permission_callback' => function() {
                    return current_user_can('edit_others_posts'); # or 'edit_ads', etc.
                },
                'callback' => function($request) {
                    ...

This means that unauthenticated users cannot use it. Plugins such as the JWT one should set the current user when you send that token, thus enable WP to assess that permission normally.

Your Answer

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

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