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 or wget. Your previous CLI command might look like the following code:

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, 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 request, which you 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 accessing 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 are accessing the frontend through your web browser, WordPress will try to redirect you to HTTPS.

Inside your .htaccess file you could go with setting up RewriteRules or SetEnvIf conditionals to check the requested route and the current user agent. But a better way is to check 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 from the browser to the webserver.

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 to the webserver. 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 HTTP request. If this is the case, we will set the HTTPS environment variable. This can be done in your .htaccess file with

SetEnvIf Authorization (.+) HTTPS=on

This does only work if your PHP environment supports the getallheaders() function. 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 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 WooCommerce’s REST authentication workflow.

See also the method WC_REST_Authentication::get_authorization_header().

Accessing your WordPress instance through your web browser still works because for the normal login endpoints at /wp-login.php and /wp-admin 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