Page MenuHomePhabricator

OAuth 2.0 non-confidential clients cannot use refresh tokens without client secret
Open, Needs TriagePublic

Description

MediaWiki-extensions-OAuth supports non-confidential clients, which can get an access token based on only their client ID. However, those clients can’t refresh the access tokens with only their client ID – they get a refresh token, but trying to use it will yield an authentication error. Since the access token has the same TTL as for confidential clients (14400 seconds), non-confidential clients only have two options to make authenticated requests beyond that TTL: ship the client secret together with the client ID (making it not secret), or repeat the authorization flow every four hours.

This is a limitation inherited from the underlying league/oauth2-server package, where you can see the difference between auth code and refresh token handlers:

AuthCodeGrant.php
// Only validate the client if it is confidential
if ($client->isConfidential()) {
    $this->validateClient($request);
}
RefreshTokenGrant.php
// Validate request
$client = $this->validateClient($request);

It’s tracked upstream as issue #1073, but I thought it would be useful to have a task on Phabricator too. (Note that Extension:OAuth and mediawiki/vendor.git currently use a custom “9.0.0-alpha” version of the library, so even if the library implements this feature, switching to that release version might not be trivial depending on how much it diverges from the version we currently use.)

Event Timeline

Although MediaWiki still generates a secret key for non-confidential OAuth 2.0 consumers (idk why), and if you use that secret key, you can still successfully use the refresh token.

On the one hand, that means you’re using the non-confidential client exactly like a confidential one, which seems kind of pointless.

But on the other hand… I guess you could just treat the client secret of a non-confidential client as another piece of public information, and publish it together with the client ID?

There's some discussion here - the behavior of using refresh tokens with public clients is not super well-defined in the spec, but it's reasonable to expect the refresh_token grant to work without the (known to be insecure) client authentication in the case of public clients. But it's not something we'd want to fork upstream over, since clients can just use the client secret and everything will work fine, so this seems like a non-issue to me (other than maybe clarifying the documentation).

It’s tracked upstream as issue #1073

Apparently this was fixed upstream in #1420, which is included in the 9.2.0 release that was published last Saturday, so at some point we’ll hopefully get that fix pulled into mediawiki/vendor. (Though we’ll need to test it first, of course, which I haven’t done in the slightest so far.)

LucasWerkmeister renamed this task from OAuth 2.0 non-confidential clients cannot use refresh tokens to OAuth 2.0 non-confidential clients cannot use refresh tokens without client secret.Feb 28 2025, 12:46 PM
LucasWerkmeister updated the task description. (Show Details)

We're working towards enabling the native apps (Android and iOS) to use OAuth 2, and it looks like we might be blocked on this issue, since apps must necessarily be non-confidential clients, and also must be able to use refresh tokens.
Since it looks like the issue was fixed upstream, are there plans to pull in the latest upstream dependency?
(Or would you recommend we ship the client secret along with the client ID?)

(Or would you recommend we ship the client secret along with the client ID?)

As far as I understand, there’s basically no reason not to ship the client secret. You shouldn’t have to, but also it shouldn’t hurt, so you might as well do it to work around this task. (See also T323867.)

It would be nice to fix this by the time apps need production OAuth client credentials, but my impression is also that there's no harm in shipping the secret. In theory, you could require some more sensitive things to go through the server and use the secret as a proof, and publishing the secret makes that impossible; but in practice, we don't support that kind of thing anyway.

Apparently this was fixed upstream in #1420, which is included in the 9.2.0 release

We use a fork of 9.0.0.
https://github.com/thephpleague/oauth2-server/compare/9.0.0...9.2.0

So it sounds like the proper fix for this is blocked on either fixing T261462: Migrate OAuth extension back from wikimedia/oauth2-server fork to upstream or at least upgrading our fork. Which seems messy, I left some notes at T261462#11034161.