WooCommerce returns an HTTP 401 error if you are not using HTTPS or the Authorization header is not parsed correctly by your webserver.

Receiving HTTP 401 during authentication

WooCommerce provides an extensive REST API with which you can automate your WordPress WooCommerce shop. WooCommerce uses a Consumer Key and Consumer Secret as credentials. After you have generated both, you might receive the error

HTTP/1.1 401 Unauthorized

{
  "code": "woocommerce_rest_cannot_view",
  "message": "Sorry, you cannot list resources.",
  "data": {
    "status": 401
  }
}

when calling the REST endpoint with curl, wget or your favorite API implementation like

curl -i  -u ${CONSUMER_KEY}:${CONSUMER_SECRET} "http://127.0.0.1/wordpress/wp-json/wc/v3/products"

# or
curl -i "http://127.0.0.1/wordpress/wp-json/wc/v3/products?consumer_key=${CONSUMER_KEY}&consumer_secret=${CONSUMER_SECRET}"

Solutions for fixing the HTTP 401 error

Check Consumer Key and Consumer Secret

The most obvious fix is to check that the Consumer Key and Consumer Secret are correctly pasted.

  • Each of these must have a length of 38 bytes (or ASCII characters).
  • The Consumer Key starts with the prefix ck_
  • The Consumer Secret starts with the prefix cs_

If your Consumer Secret contains a null-byte or newline or carriage return byte at the end of the string, you will receive the error Consumer secret is invalid instead:

HTTP/1.1 401 Unauthorized

{
  "code": "woocommerce_rest_authentication_error",
  "message": "Consumer secret is invalid.",
  "data": {
    "status": 401
  }
}

HTTPS is an requirement for non-OAuth signed requests

WooCommerce’s authentication procedure is defined in woocommerce/includes/wc-rest-authentication.php. The method authenticate($user_id) requires that you use SSL/TLS if you are not using OAuth 1.0a:

if ( is_ssl() ) {
	$user_id = $this->perform_basic_authentication();
}

OAuth 1.0a requires a signing of your HTTP requests which are probably not doing when using curl.

Using consumer_key and consumer_secret as GET parameters

When calling the API with

curl -i "http://127.0.0.1/wordpress/wp-json/wc/v3/products?consumer_key=${CONSUMER_KEY}&consumer_secret=${CONSUMER_SECRET}"

we have to let WordPress know that HTTPS is enabled, even if it is not.

There are mulitple solutions, all involve setting the PHP environment variable $_SERVER[‘HTTPS’] to on. Geting an X.509 certificate for your local environment and access your WooCommerce instance through HTTPS might be tricky.

But you can set the environment variable in the .htaccess file when using Apache with

SetEnv HTTPS on

or you could change WordPress index.php to set the variable:

/**
 * Tells WordPress to load the WordPress theme and output it.
 *
 * @var bool
 */
define( 'WP_USE_THEMES', true );

$_SERVER['HTTPS'] = 'on';

/** Loads the WordPress Environment and Template */
require __DIR__ . '/wp-blog-header.php';

Both solutions do only work for your command line/API usage. As soon as you access the frontend through your web browser, WordPress will try to redirect you to HTTPS.

Instead of dealing with RewriteRules or SetEnvIf conditionals which are dealing with the route or user agent, you should go with using the Authorization header.

Using the Authorization header

From a developer’s point of view this should be the way to go anyway. The HTTP Authorization header is designed for exactly this: Providing authentication credentials.

When using

curl -i  -u ${CONSUMER_KEY}:${CONSUMER_SECRET} "http://127.0.0.1/wordpress/wp-json/wc/v3/products"

curl passes the credentials as an HTTP Authorization request header. This is the base64-encoded sample for my local test environment:

Authorization: Basic Y2tfYjY2Nzc1NzZiMmI2NzIxODJiMTVhNGYwM2Y4NmNlYjliOGMxYzgxNzpjc19mNzA3ZDA4ZDEwMzM4NDRkODEwNTZlMDYxY2QxOTgzODY3NTQ2MjNk

We are now looking if the Authorization header is present in the client’s request and if yes, we will set the HTTPS environment variable. This can be done in an .htaccess file with

SetEnvIf Authorization (.+) HTTPS=on

This does only work if your PHP environment supports the getallheaders() If you are using PHP-FPM before 7.2.0, you have to to use the HTTP_AUTHORIZATION header like

# Set HTTP_AUTHORIZATION header if Authorization is passed by the client
SetEnvIf Authorization (.+) HTTP_AUTHORIZATION=$1

For nginx you would have to use

fastcgi_pass_request_headers on;
fastcgi_pass_header Authorization;

This passes the Authorization header to to PHP FPM. In the end it will populate the environment variables PHP_AUTH_USER and PHP_AUTH_PW. Both environment variables are also used as fallback in the REST authentication workflow.

See also WC_REST_Authentication::get_authorization_header().

Accessing your WordPress instance through your webbrowser still works because for the login Form Authentication is used.

I am asking you for a donation.

You liked the content or this article has helped and reduced the amount of time you have struggled with this issue? Please donate a few bucks so I can keep going with solving challenges.

Categories: WordPress