Summary: A custom error structure returned by the Wikimedia infrastructure for requests with an expired OAuth 2 access token prevents m3api-oauth2, and possibly other MediaWiki OAuth 2 libraries, from automatically refreshing the token and retrying the request.
Steps to replicate the issue (include links if applicable):
- git clone https://gitlab.wikimedia.org/repos/m3api/m3api-examples.git (m3api-examples)
- cd m3api-examples/webapp-clientside-vite-guestbook OR cd m3api-examples/webapp-serverside-express-guestbook (both are affected but will display the error in different ways; the client-side example offers more visibility because you can see all the relevant request in the browser’s dev tools)
- fresh-node -- npm install
- fresh-node -net -- npm run start
- go to http://localhost:8080/
- log in via OAuth
- optionally, click the “sign guestbook” button right now, just to confirm that it works; this should make a successful edit to the m3api-examples guestbook page
- wait four hours for the access token to expire
- if you’re feeling very adventurous, I suppose you could configure a lower $wgOAuth2GrantExpirationInterval (example) on mw-experimental and hack m3api (e.g. in node_modules/m3api/fetch.js) to include a request header like X-Wikimedia-Debug: backend=mw-experimental, then wait for a shorter expiration interval (you might need to do this before logging in, I’m not sure)
- click the “sign guestbook” button
What happens?:
Envoy(?) returns a response with the HTTP status code 401 (Unauthorized) and the JSON body {"httpCode":401,"httpReason":"Jwt is expired"} (probably Envoy::JwtVerify::Status::JwtExpired). If I understand correctly, the request never even makes it to MediaWiki. (A similar error was previously seen in T417839.)
In the client-side example, this will result in a CORS error because the response does not have an Access-Control-Allow-Origin header. m3api’s code can’t even see the response.
In the server-side example, m3api detects this as an unexpected, non-MediaWiki error (because the status code is not 200 and there is no MediaWiki-API-Error response header) and aborts. m3api-oauth2 can’t register a handler for this kind of error.
What should have happened instead?:
The guestbook should be signed successfully.
The expected MediaWiki response to the request with the expired access token looks something like this:
{ "errors": [ { "code": "mwoauth-invalid-authorization" } ] }
(Though the exact format will vary depending on the request parameters, mainly the errorformat=.) m3api-oauth2 registers an error handler for this error code which automatically refreshes the access token and then retries the request, transparently to the application.
Software version (on Special:Version page; skip for WMF-hosted wikis like Wikipedia):
N/A, I think this problem is only present in Wikimedia infrastructure (I test the automatic refresh against a local wiki in CI and it still works there).
Other information (browser name/version, screenshots, etc.):
In the server-side example, the error looks like this:
API request returned non-200 HTTP status code: 401
Error: API request returned non-200 HTTP status code: 401 at NodeSession.request (file:///webapp-serverside-express-guestbook/node_modules/m3api/core.js:569:10) at process.processTicksAndRejections (node:internal/process/task_queues:95:5) at async NodeSession.requestAndContinue (file:///webapp-serverside-express-guestbook/node_modules/m3api/core.js:625:21) at async NodeSession.getToken (file:///webapp-serverside-express-guestbook/node_modules/m3api/core.js:706:22) at async NodeSession.request (file:///webapp-serverside-express-guestbook/node_modules/m3api/combine.js:28:4) at async file:///webapp-serverside-express-guestbook/routes/index.js:51:19