indieweb / indieweb/wordpress-indieauth

determine_current_user hook is unscoped — validates/rejects Bearer tokens intended for other plugins' REST routes

Open
#329 6 comments 0 reactions 0 assignees View on GitHub
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.