Page MenuHomePhabricator

Create a mediawiki maintenance script for generating JWT tokens
Closed, ResolvedPublic

Description

Being able to generate valid JWT tokens with arbitrary payloads on the command line is useful for testing an debugging. It should be really easy to do...

@Tgr shared the following implementation outline:

$jwtCodec = MediaWikiServices::getInstance()->getJwtCodec();
if ( !$jwtCodec->enabled() ) { /* the wiki admin did not set up the RSA keys */ }
$token = $jwtCodec->create( $claimArray );

The script should support creating tokens with the "normal" payload fields that would be used when creating a token for use in a session cookie (see T399198), but allow overriding individual claims on the command line (or from a file?)

Event Timeline

We don't have a way right now to get the normal payload; JWT claim generation is an internal detail of session providers. (And the OAuth extension has its own separate way of generating tokens, because it ties so deeply into league/oauth-server.) SessionManager::getJwtData() centralizes some claims (including sub and whatever we'll do for rate limiting classes), but some details are left to individual session providers (because e.g. expiry needs to depend on what session mechanism is used).

For cookies, the claim generation is easy to access (we'd just have to make CookieSessionProvider::getJwtClaimOverrides() public); for OAuth, it's distributed in various corners of league/oauth-server and the adapter code in the extension, so reproducing that might be less straightforward.

Actually for OAuth 2, the jti claim is looked up in a database, so there isn't any easy way to generate valid tokens - you need to generate actual oauth2_access_tokens table entries, at which point it might be easier to just use a real OAuth app. (There is a createOAuthConsumer.php maintenance script for creating apps programmatically; it doesn't quite return access tokens, but that would be an easy addition.)

For session cookies, generating a valid token is easily doable, but note that a JWT session cookie won't actually authenticate a request to MediaWiki on its own, it is (for now) validated on top of the existing cookies, so you still need a session ID or a user token cookie if are trying to the a real request.

(For OAuth 2 there's also the oauth2/client REST endpoint which can also be used to generate apps programmatically.)

We have two types of tokens - the MediaWiki one and OAuth2 one.

  • MediaWiki token can be easily generated via code and it's couple lines of code. Also this JWT token is not used to authenticate users on the MW. It's used to determine whether user has a valid session but session is still created/fetched based on the regular session cookie.
  • OAuth2 can generate JWT Tokens too, but those are stored in database and tracked. There is no easy way to generate token programmatically (needs to recreate entire flow and insert token to DB), therefore most likely it would be better/easier just to create a sample app, and use that app to get tokens.

We will be focusing just on first part, creating a session token. I can see two options here

a) expect operator (person who calls the script) to pass all claims, most likely in the form of json string or maybe point to a file containing json. This way we have the full flexibility over claims. As this JWT token would be used only to verify the session presence/checking the rate limiter class - it should be more than enough. But in case of any future development the maintenance script wouldn't create a workable JWT token unless operator passes correct data with all required claims.

step 1 - $data = read input and/or file_get_contents()
step 2 - log use of the tool
step 3 - $token = JWTCodec::encode( $data )
step 4 - echo the token

b) Another approach would be to use the SessionManager::getJwtData( UserIdentity $user ) and CookieSessionProvider::getJwtClaimOverrides( $expirationDuration ). This way script would use the MediaWiki to initialize all required claims, in long run this would make the maintenance script reliable and usable in long run. Script would require passing user_id as an argument with a possibility to pass additional claims as additional options. Script would use the GetSessionJwtData hook to inject additional claims and return a valid JWT token with all claims.
This solution requires bit more work and a possible refactor/changes in CookieSessionProvider but provides a more robust solution.

@daniel do you have any thoughts on which approach would be better? quick and dirty - all claims defined by an operator? Or do we want to make this maitenance script usable and create a working and usable tokens ( with some additional claims defined by operator).

@daniel do you have any thoughts on which approach would be better? quick and dirty - all claims defined by an operator? Or do we want to make this maitenance script usable and create a working and usable tokens ( with some additional claims defined by operator).

I'd suggest to start simple, and add the ability to be "smart" later

Change #1196400 had a related patch set uploaded (by Pmiazga; author: Pmiazga):

[mediawiki/core@master] maintenace: Introduce GenerateJWT maintenance script

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

Change #1196400 merged by jenkins-bot:

[mediawiki/core@master] maintenace: Introduce GenerateJwt maintenance script

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

Change #1198280 had a related patch set uploaded (by Pmiazga; author: Pmiazga):

[mediawiki/core@master] maintenance: Introduce ExtractClaimsFromJwt script

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

Change #1198280 merged by jenkins-bot:

[mediawiki/core@master] maintenance: Introduce ExtractClaimsFromJwt script

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