Page MenuHomePhabricator

Allow authenticated requests via OAuth to the Action API from any origin
Open, Needs TriagePublic

Description

I have recently tried creating a front-end application that would make authenticated requests to the Action API using OAuth credentials. At this stage, I am only using owner-only consumers and it would prompt for access token.

When making a request using the Authorization: Bearer $TOKEN header, the CORS request will fail.

There is also a response header from the API.

mediawiki-cors-rejection: Unsupported header requested in preflight

I don't see how credential stealing could be an issue when we are using OAuth based authentication and does not involve cookies.

Event Timeline

Somewhat related: T126257: The API should not require CSRF tokens for an OAuth request. SessionProviderInterface has a safeAgainstCsrf() method, we'd need to check that in ApiMain::handleCors() and always set the CORS header for such a request.

The one thing I would worry about is that such requests might still have authentication cookies set, and maybe something somewhere could be somehow tricked into using the cookies instead of the bearer token? In theory seems impossible as session authentication is centralized in MediaWiki and OAuth takes precedence over cookies.

Also relevant to T234677: Support Free and Open Source software API clients with OAuth 2.0, I think (a FLOSS browser-based application using a non-confidential OAuth client should be able to make authenticated cross-origin requests, similar to an owner-only client).

Yeah, I just tried to create a client-side web app with a non-confidential OAuth 2.0 client (example code) and ran into this issue again: the OAuth “handshake” completes, but the Authorization header isn’t allowed in the actual Action API requests.

Change 919386 had a related patch set uploaded (by Lucas Werkmeister; author: Lucas Werkmeister):

[mediawiki/core@master] Add Authorization to default $wgAllowedCorsHeaders

https://gerrit.wikimedia.org/r/919386

Perhaps @sbassett or someone else from Security-Team can take a look at this? (Compare T269636 for the last $wgAllowedCorsHeaders change.)

Edit: I found a security team member at the Hackathon, we should be good :)

Change 919386 merged by jenkins-bot:

[mediawiki/core@master] Add Authorization to default $wgAllowedCorsHeaders

https://gerrit.wikimedia.org/r/919386

Legoktm assigned this task to LucasWerkmeister.

Perhaps @sbassett or someone else from Security-Team can take a look at this? (Compare T269636 for the last $wgAllowedCorsHeaders change.)

Edit: I found a security team member at the Hackathon, we should be good :)

Ok, good. This seemed fine to me anyways.

Change 921154 had a related patch set uploaded (by Reedy; author: Lucas Werkmeister):

[mediawiki/core@REL1_40] Add Authorization to default $wgAllowedCorsHeaders

https://gerrit.wikimedia.org/r/921154

Change 921155 had a related patch set uploaded (by Reedy; author: Lucas Werkmeister):

[mediawiki/core@REL1_39] Add Authorization to default $wgAllowedCorsHeaders

https://gerrit.wikimedia.org/r/921155

Change 921156 had a related patch set uploaded (by Reedy; author: Lucas Werkmeister):

[mediawiki/core@REL1_38] Add Authorization to default $wgAllowedCorsHeaders

https://gerrit.wikimedia.org/r/921156

Change 921157 had a related patch set uploaded (by Reedy; author: Lucas Werkmeister):

[mediawiki/core@REL1_35] Add Authorization to default $wgAllowedCorsHeaders

https://gerrit.wikimedia.org/r/921157

Change 921155 merged by jenkins-bot:

[mediawiki/core@REL1_39] Add Authorization to default $wgAllowedCorsHeaders

https://gerrit.wikimedia.org/r/921155

Change 921154 merged by jenkins-bot:

[mediawiki/core@REL1_40] Add Authorization to default $wgAllowedCorsHeaders

https://gerrit.wikimedia.org/r/921154

Change 921157 merged by jenkins-bot:

[mediawiki/core@REL1_35] Add Authorization to default $wgAllowedCorsHeaders

https://gerrit.wikimedia.org/r/921157

Change 921156 merged by jenkins-bot:

[mediawiki/core@REL1_38] Add Authorization to default $wgAllowedCorsHeaders

https://gerrit.wikimedia.org/r/921156

So CORS with Authorization: works now, but my example webapp still doesn’t work, I’m afraid:

  • with &origin=* in the URL, the request is allowed but treated as anonymous
  • without &origin=*, the request is not allowed (no access-control-allow-origin in the OPTIONS response)

I’m reopening this task, since I think it’s broad enough to cover this problem too: we still don’t “[a]llow authenticated requests via OAuth to the Action API from any origin”.

But I’m not sure what the solution should be, actually. Should the Authorization: header override &origin=, making an exception to the long-standing rule that requests with &origin=* are always treated as anonymous (T62835)? Or should requests with Authorization: be allowed cross-site even without &origin=* in the URL? (I’m not even sure that’s possible… the Authorization: header isn’t part of the pre-flight OPTIONS request, that only has Access-Control-Request-Headers: api-user-agent,authorization.)

origin is not a security measure, it's a cache management measure. (Or, I guess, it's a security measure but it's specifically for preventing cache poisoning/leaking.) I guess with Authorization it isn't strictly necessary because that header makes the response uncacheable (and if it doesn't, we have bigger problems) but I'd still rather not mess with it. Is there any reason you can't just set the origin properly?

origin is not a security measure, it's a cache management measure. (Or, I guess, it's a security measure but it's specifically for preventing cache poisoning/leaking.)

Well, it is in that it can force unauthenticated requests via origin=*. So if you have a system where you want to forbid people from attempting auth'd requests to prevent potential (or unknown) data leaks, etc. it can be used as a sort of sledgehammer mitigation.

Is there any reason you can't just set the origin properly?

If I add &origin=http://localhost:8080, the OPTIONS request produces mediawiki-cors-rejection: Origin mismatch (and no access-control-allow-origin response header), because http://localhost:8080 is not in $wgCrossSiteAJAXdomains.

Change 926663 had a related patch set uploaded (by Lucas Werkmeister; author: Lucas Werkmeister):

[mediawiki/core@master] [POC] Allow non-anonymous CORS for some session providers

https://gerrit.wikimedia.org/r/926663

Change 926663 had a related patch set uploaded (by Lucas Werkmeister; author: Lucas Werkmeister):

[mediawiki/core@master] [POC] Allow non-anonymous CORS for some session providers

https://gerrit.wikimedia.org/r/926663

I’ve tested this change against my local wiki, and as far as I can tell, it works. What I don’t know is whether it’s safe – this should definitely be scrutinized by others :)

(One question to look into: is SessionProvider::safeAgainstCsrf() the right method to check? It seems related, at least, and it’s correct for our purposes that it returns false for normal cookie-based sessions but true for OAuth sessions, but perhaps it would be better to introduce a new method that returns true in OAuth but not in, say, CentralAuthHeaderSessionProvider.)

I updated the change a little bit to move it out of proof of concept territory (update the API doc message, add a test).