Repository navigation
determine_current_user hook is unscoped — validates/rejects Bearer tokens intended for other plugins' REST routes #329
Description
Plugin version: 4.7.2
Summary
IndieAuth\Authorize::load() hooks determine_current_user unscoped:
\add_filter( 'determine_current_user', array( $this, 'determine_current_user' ), 15 );
\add_filter( 'rest_authentication_errors', array( $this, 'rest_authentication_errors' ) );This fires on every WordPress request, not just requests to IndieAuth's own routes. determine_current_user() calls get_provided_token(), which reads any Authorization: Bearer ... header present (via $_SERVER['HTTP_AUTHORIZATION'], REDIRECT_HTTP_AUTHORIZATION, or a getallheaders() fallback) and runs it through verify_access_token() — regardless of which plugin or endpoint the token was actually issued for.
If that token isn't a real IndieAuth token, verify_access_token() returns an invalid_token OAuth_Response, which gets stored on $this->error and then surfaced by rest_authentication_errors() as the request's authentication error — before the actual target route's own permission callback or logic ever runs. This produces a 401/error response for a request that has nothing to do with IndieAuth.
Concrete case we hit
Enable Mastodon Apps' Site Health test for its own Authorization-header diagnostic sends a synthetic, intentionally-invalid value (Bearer ema-health-check) to a dedicated diagnostic REST route, specifically to check whether the header physically arrives at WordPress. With IndieAuth active, this synthetic Bearer value gets validated (and rejected) by IndieAuth's unscoped hook before EMA's own diagnostic route logic gets a clean look at the header — producing a false negative ("Authorization headers do not reach WordPress") on a host where header transport is actually working fine. Full root-cause writeup: akirk/enable-mastodon-apps#324
More generally, this affects any plugin that issues or expects its own Bearer tokens on its own REST routes: IndieAuth's check runs first, unconditionally, and can reject a token that was never meant to be validated against IndieAuth's token store at all.
Precedent
Enable Mastodon Apps had the identical unscoped-hook shape on its own OAuth class and fixed it by scoping determine_current_user to only run on its own routes (akirk/enable-mastodon-apps#325, merged) — checking the request's REST route against its own prefix before attempting token validation, and returning the passed-through $user_id unchanged otherwise.
Suggested fix
Scope determine_current_user() (and correspondingly rest_authentication_errors()) so token validation is only attempted when the current request is actually targeting an IndieAuth-owned route. The codebase already has this pattern elsewhere — Authorize::return_oauth_error() checks:
if ( 0 !== strpos( $request->get_route(), '/indieauth/1.0/' ) ) {
return $response;
}Applying an equivalent guard to determine_current_user() before calling get_provided_token()/verify_access_token() would prevent IndieAuth from intercepting Bearer tokens meant for other plugins' routes, while leaving its own protected endpoints unaffected.
Workaround in place
We're currently running a small site-specific compatibility plugin that temporarily hides the Authorization header from $_SERVER and IndieAuth's getallheaders() fallback, but only for the one EMA diagnostic route, then restores it immediately after. Happy to remove that once this is scoped upstream — flagging here in case it's useful as a live reproduction case.
Activity
dshanske commentedon Sep 6, 2026
@lhero-org What exactly is an IndieAuth scoped route? IndieAuth is used to provide access to any authenticated route. Mastodon only has specific routes it uses. This solution wouldn't work here.
dshanske commentedon Sep 6, 2026
It seems like there are a few things that might work here. @pfefferle would welcome your thoughts.
I think scoping IndieAuth to only work with itself makes it useless the same way scoping any OAuth type plugin to do that would.
pfefferle commentedon Sep 7, 2026
I think we had something similar in the past? Isn't it possible to pass the info through, so that another plugin can try to authenticate? Isn't that a filter queue?
kasparsd commentedon Sep 8, 2026
I was about to report the same issue. The OAuth2 integration I'm using for an MCP integration is reporting authentication failure on custom REST API routes where the client is sending a Bearer token that is invalid according to indieauth checks.
I wonder if we could make indieauth skip "invalid" Bearer tokens and let the REST API return an error, if the authorization callback for those routes fail.
pfefferle commentedon Sep 8, 2026
To answer my own question: yes, it is a filter queue, and #245 and #263 (both by @akirk) already made the plugin pass the user through so another plugin can authenticate. What was never handled is the case where nobody recognizes the token. IndieAuth still stores an
invalid_tokenerror and returns it fromrest_authentication_errors, so the request fails before the route gets a say. @dshanske actually asked about that case in #245, we just never followed up.That is basically what @kasparsd suggested. I opened #330: unknown tokens are passed through and the route decides. If the route rejects the request with a 401 anyway, the plugin swaps in the
invalid_tokenerror, so Micropub clients still get the RFC 6750 response. Public routes (like the EMA health check) and routes that return their own error are not touched. No gate, IndieAuth tokens keep working on every route.One thing it does not solve: Micropub returns a 403
forbiddenwhen no user is logged in, so an invalid token there now gets the Micropub error instead of ours. I think that should be fixed in Micropub, it should be a 401 anyway. I will open a follow-up there.dshanske commentedon Sep 8, 2026
2023 me was on top of things