skip to Main Content

I need to add an API endpoint in my WordPress plugin that will write to the database, etc. I want it to be triggered by a remote system that will call this new API.

However, I’d like to use the built-in WooCommerce REST authentication, the auth set up here:

enter image description here

(And no, those credentials don’t work, anymore).

Is there a way to do this? To add a custom HTTP endpoint that will use the auth from WooCommerce and then run some arbitrary code that I want to run?

Or is there a way to add a hook on an existing WooCommerce API endpoint to run before that endpoint executes? I just need a way to use the existing WooCommerce REST API auth to trigger some updates I need to run in my plugin.

3

Answers


  1. You can add a route under the Woocommerce endpoint to use this key/secret authentication system.

    Here’s a working example:

    add_action('rest_api_init', function () {
        register_rest_route('wc', '/test', [
            'methods'  => 'GET',
            'callback' => 'my_awesome_func',
        ]);
    });
    
    function my_awesome_func(WP_REST_Request $request)
    {
    
        if (is_user_logged_in() && (int)wp_get_current_user()->ID === 123) {
            //your stuff only for legged in user 123
            return new WP_REST_Response('ok', 200);
        }
    
        return new WP_Error('unauthorized', __('You shall not pass'), [ 'status' => 401 ]); //can also use WP_REST_Response
    
     }
    

    This will:

    • add a new GET route under the "wc" endpoint (woocommerce one), so https://example.com/wp-json/wc/test
    • you then use "HTTP Basic auth" to pass your key and secret as per Woocommerce documentation
    • the results of is_user_logged_in() and wp_get_current_user() will depend on the correct combination of key/secret. If correct, a the associated user will be "authentified" as for WordPress.
    • if correct combination you’ll get [true, WP_user_object], if incorrect key [false, Empty_user_object].
    • If correct key and incorrect secret, a 401 will be thrown (not the one in my example, another 401 by woocommerce. my_awesome_func() is not reached in this case. I believe it’s like wp-login, custom error message when correct login (key), nice ^^’)

    You then need to secure your route by your rules:

    • Checking if user is logged in like if(!is_user_logged_in()) return false
    • Checking user ID wp_get_current_user()->ID
    • Checking by your role/permission system
    • now that you have user ID, you do what you want with it
    • use the WP_REST_Request $request parameter to handle GET or POST HTTP parameters.

    So it could look like:

    add_action('rest_api_init', function () {
        register_rest_route('wc', '/test', [
            'methods'  => 'GET',
            'callback' => 'my_awesome_func',
        ]);
    });
    
    function my_awesome_func(WP_REST_Request $request)
    {
    
        if (is_user_logged_in() && (int)wp_get_current_user()->ID === 123) {
            //your stuff only for legged in user 123
            return new WP_REST_Response('ok', 200);
        }
    
        return new WP_Error('unauthorized', __('You shall not pass'), [ 'status' => 401 ]); //can also use WP_REST_Response
    }
    

    It seems there may be proper ways to add a Woocommerce REST API endpoint (couldn’t find proper documentation about it…). But I tested this and it worked, at least to use the Woocommerce key/Secret authentication system, which I believe is applied under the /wc/ endpoint.

    Note: careful adding routes to the wc endpoints, as you may override an existing route endpoint. Eg: adding /product/mytest could conflict with an official Woocommerce route that handles /product/[product_id].

    Note 2: I first tested with a custom WordPress REST API route, and passing Woocommerce key/secret to see if WordPress could see me correctly authenticated and identified. But it didn’t work (WP uses the core API auth system). So I moved to the Woocommerce API endpoint.

    Note 3: You could achieve the same using the WordPress REST API with a custom route, and a "machine to machine" authentication plugins like Oauth, application password, JWT… like described on WP REST API authentication documentation page.

    Login or Signup to reply.
  2. A bit late but with reflection we can use WC auth logic to validate custom API routes.

    add_action( 'rest_api_init', function () {
        /**
         * Register here your custom routes for your CRUD functions
         */
        register_rest_route( 'test/', 'wc-permissions', array(
            array(
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => 'read_check',
                'permission_callback' => 'do_wc_basic_auth_permission_check'
            ),
            array(
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => 'write_check',
                'permission_callback' => 'do_wc_basic_auth_permission_check',
            ),
        ) );
    } );
    
    /**
     * Perform check via official WC REST auth class by reflecting method accessibility.
     * Uses official auth class, so it triggers all hooks as on normal 'wp-json/wc/v3/' routes
     *
     * @return bool
     * @throws ReflectionException
     */
    function validate_request_has_valid_wc_api_keys(): bool {
        // WC not enabled, target reflection class is probably not registered
        if ( ! function_exists( 'WC' ) ) {
            return false;
        }
    
        $method = new ReflectionMethod( 'WC_REST_Authentication', 'perform_basic_authentication' );
        $method->setAccessible( true );
    
        return $method->invoke( new WC_REST_Authentication ) !== false;
    }
    
    function write_check() {
        return 'can create new entry via POST';
    }
    
    function read_check() {
    
        return 'can get entry content via GET';
    }
    

    If you would like do add this authentication checks to other routes globally, f.e. core WP routes, you can use add_filter('rest_dispatch_request', function ( $intendedResponse, $request, $route ) filter and try to match passed $route param with regex or some other way.

    Login or Signup to reply.
  3. Just putting my two cents in…

    I was wondering if there’s another way to add a new API endpoint which uses WooCommerce consumer key and secret authorization, avoiding possible collisions with WooCommerce /wc/ endpoints, and not resorting to using reflection.

    The answer is in the is_request_to_rest_api function included in the WC_REST_Authentication class. In order to use WooCommerce authentication method, the endpoint namespace of third-party plugins should start with wc-.

    E.g.:

    add_action( 'rest_api_init', function() {
      register_rest_route(
        'wc-myprefix/v1',
        '/orders/(?P<order_id>d+)/shipment-trackings',
        array(
          'methods'             => WP_REST_Server::READABLE,
          'callback'            => 'get_shipment_trackings',
          'permission_callback' => 'check_permission',
        ),
      );
    });
    
    function get_shipment_trackings( WP_REST_Request $request ) {
        // return shipment trackings
    }
    
    function check_permission() {
        if ( ! current_user_can( 'read_private_shop_orders' ) ) {
            return new WP_Error( 'woocommerce_rest_cannot_view',
                __( 'Sorry, you cannot list resources.', 'myprefix' ),
                array( 'status' => rest_authorization_required_code() ) );
        }
        return true;
    }
    

    Please note the wc-myprefix/v1 namespace of the new route (starting with wc-), and the use of the current_user_can function in the permission callback. It’s the call to this function that triggers the actual authentication.

    And if you’re like me and don’t really want anybody telling you how your endpoint namespace should be named, there’s a handy filter (woocommerce_rest_is_request_to_rest_api) that you can use to implement your own logic to determine whether the call is to a rest API endpoint (i.e. check for your own particular endpoint namespace).

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search