The Role That Wasn't the Problem
A colleague couldn't log into the WooCommerce app. I spent an afternoon fixing her user role. The role was never it — the server was eating the auth header.
- WooCommerce
- WordPress
- REST API
- Debugging
A colleague couldn’t log into the WooCommerce mobile app. Every sign pointed at her
user role. I changed the role three times. The role was never the problem — the server
was quietly eating the Authorization header.
It started, as these things do, with a screenshot in a chat.
She was trying to sign into the WooCommerce iOS app with her store credentials. Address correct, username correct, password correct. And then a dark little toast at the bottom of the screen:
We couldn’t verify your role on this store. Please try again. If the problem persists, contact support.
“Couldn’t verify your role.” The app is practically pointing at the answer. Her
account had a custom role — admin_manager — and the WooCommerce app only understands
a handful of standard ones: administrator, shop_manager, editor. A custom role
it’s never heard of? Of course it can’t verify it. Case closed.
Except it wasn’t.
The theory that fit too well
This is the trap, and I walked straight into it: the error message agreed with my first theory, so I stopped looking.
I opened her user in wp-admin and switched the role from admin_manager to
Administrator. Clean, standard, unambiguous. Told her to try again.
Same error.
Okay — stale session. The app caches the role from the first login; changing it server-side doesn’t retroactively update a live session. So I hit “Log out everywhere” on her profile to nuke every session, and had her sign in fresh.
Same error.
Now I’m annoyed, because the theory was good. A custom role, an app that whitelists roles, an error message that literally says “role.” Everything lined up. When your explanation and the error message are this friendly with each other, it feels like confirmation. It’s not. It’s just two things pointing the same wrong direction.
Lesson #1: An error message describes the symptom the app decided to show you, not the layer where things actually broke. “Couldn’t verify your role” and “the role is wrong” are not the same sentence.
The second error that broke the story
To take her account out of the equation, I tried logging in with my own WordPress
admin account — a real, boring, unambiguous administrator. If roles were the issue,
mine would sail through.
It didn’t. But it failed differently:
Error fetching user information.
That stopped me. Two accounts, two different errors. Her admin_manager said
“couldn’t verify your role.” My clean administrator said “error fetching user
information.” If the problem were roles, my account would either work or fail the same
way hers did. A different failure on a valid admin meant the role story was dead —
I’d just been too invested to notice.
“Error fetching user information” is what the app says when it authenticates, then
goes to fetch the current user from the REST API — /wp-json/wp/v2/users/me — and
doesn’t get back what it expects.
So the question changed from “what’s wrong with the role” to “what happens when something asks this site who I am.”
Following the request instead of the message
First, the naive check. Open the endpoint in a logged-in browser:
https://shop.example.com/wp-json/wp/v2/users/me
{"code":"rest_not_logged_in","message":"You are not currently logged in.","data":{"status":401}}
A 401. For half a second that looks like a smoking gun. It isn’t — and this is the
second place the investigation tried to mislead me. Browser cookie auth over REST
needs a nonce, and a plain address-bar visit doesn’t carry one. So WordPress correctly
says “not logged in.” This 401 is normal. It only tells you the request reached
WordPress. File it away — that detail matters in about thirty seconds.
The app doesn’t use cookies anyway. It uses an Application Password — HTTP Basic
Auth in an Authorization header. So I reproduced that, with curl, using a real
application password:
curl -u "admin_dev:xxxx xxxx xxxx xxxx xxxx xxxx" \
https://shop.example.com/wp-json/wp/v2/users/me
And out came this:
<!DOCTYPE html>
<html lang="en">
<head>
<title>403 Forbidden</title>
<link rel="stylesheet" href="/error_docs/styles.css">
</head>
<body>
<div class="page"><div class="main">
<h1>Server Error</h1>
<div class="error-code">403</div>
<h2>Forbidden</h2>
<p class="lead">You do not have permission to access this document.</p>
Look at what that is. It’s not a WordPress response. There’s no {"code": ...}
JSON, no WordPress theming — it’s a bare server error page, styled from
/error_docs/styles.css. That page comes from the web server / firewall, not from
PHP. WordPress never ran. The request was killed at the door.
Now put the two requests side by side:
- Browser, no
Authorizationheader → reached WordPress, got a polite JSON401. curl, with anAuthorizationheader → killed by the server with a raw403before WordPress woke up.
The only meaningful difference between those two requests is the Authorization
header. Add Basic Auth and the request stops existing.
Lesson #2: Learn to tell who answered you. A JSON error with a
codefield is WordPress talking. An HTML page with a server error template and its own stylesheet is the web server or WAF talking — and it means your request never reached the application at all.
The actual culprit
A WAF / mod_security rule (extremely common on managed WordPress hosting) was
blocking requests to wp-json that carried an Authorization header. Basic Auth on
the REST API tripped a “someone’s trying to brute-force auth” heuristic, and the
firewall returned its own 403 before PHP ever saw the request.
Which explains everything, cleanly:
- The mobile app authenticates with Basic Auth → firewall blocks it → the app can’t fetch the user → “Error fetching user information.”
- It had nothing to do with the role. It never did.
- And the reason the “role” error looked so convincing on the first account is that it was a different app build showing a different generic message for the same underlying failure to read the user.
The role was a coincidence dressed up as a cause.
Lesson #3: When two people hit “the same bug” with two different error messages, don’t average them into one theory. The discrepancy is the clue. Identical root causes tend to produce identical failures.
The fix
This one doesn’t live in the theme or in WordPress at all — it’s the hosting layer:
- Whitelist Basic Auth on the REST API. Ask the host (or edit the WAF /
mod_securityconfig) to allow anAuthorizationheader on/wp-json/wp/v2/usersand/wp-json/wc/*. That’s the durable fix. - Confirm the header even survives to PHP. Some stacks strip
Authorizationbefore it reaches WordPress; a one-line.htaccessrewrite puts it back:
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
- Rule out a User-Agent block while you’re there. Firewalls love to
403anything that looks likecurl. Re-run the request with a browser UA (curl -A "Mozilla/5.0" ...); if that suddenly works, the block was on the User-Agent, not the header — a different rule, same lesson.
What I actually took away
I lost an afternoon not to a hard problem but to an agreeable one. The very first error message handed me a theory, the user’s account happened to have a matching quirk, and the two reinforced each other until I mistook coincidence for confirmation. I changed a role three times. I logged sessions out. I did everything the story wanted — and the story was fiction.
The moment I actually made progress was the moment a second, different error showed up on an account that had no business failing. That contradiction was worth more than any log line, because it broke the comfortable narrative and forced me to follow the request itself instead of the words on the screen.
This is the same shape of bug as the day fail2ban banned our whole office: the application was innocent, the infrastructure in front of it was the one making decisions, and every minute spent inside the app was a minute spent in the wrong building.
Watch who answers you. WordPress speaks JSON. The firewall speaks HTML. And when a request dies before the application even sees it, no amount of fixing the application will ever help.