indieweb / indieweb/wordpress-indieauth
determine_current_user hook is unscoped — validates/rejects Bearer tokens intended for other plugins' REST routes
- Dominant language
- PHP
- Stars
- 36
- Forks
- 14
- Avg merge
- 2h 6m
- Merged PRs (30d)
- 3
Description
**Plugin version:** 4.7.2
### Summary
`IndieAuth\Authorize::load()` hooks `determine_current_user` unscoped:
```php
\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: https://github.com/akirk/enable-mastodon-apps/issues/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:
```php
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.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with IndieAuth\Authorize::load(), determine_current_user(), and rest_authentication_errors(), then compare their behavior with the route guard in Authorize::return_oauth_error(). Reproduce the issue on a non-IndieAuth REST route using a Bearer header, and verify that unrelated routes pass through while IndieAuth-owned routes still validate tokens.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php
- Domain
- api, authentication
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 72/100