Ghost JWT question, possible bug?

I was working with getting a jwt from ghost to authorize access to a Members-Only enhanced search. From previous posts, etc, the workflow is the user signs in, then we send a GET request to members/api/session, and then submit that JWT. This works fine except…

The JWT provided by Ghost seems to have an error. The kid claim is in the body instead of the header where it is expected. kid is an optional JWS header parameter per RFC

If I’m confused then please let me know but this seems wrong and breaks compatibility with libraries that expect to load the kid from the header to match to the JWKS that Ghost publishes.

Python example (pyjwt)

jwks_client = jwt.PyJWKClient('<my_url>/members/.well-known/jwks.json')
jwks_client.get_signing_key_from_jwt(current_jwt)
jwt.exceptions.PyJWKClientError: Unable to find a signing key that matches: "None"

On a side note I don’t see any sort of membership tiers in here. Are there? Mine do not seem to be showing up. It would be really amazing if they did!

Hey @elijahsgh you’re right that’s absolutely a bug. We’ve got it on our radar to fix now :)

1 Like

Thanks so much!

On the possible enhancement side what about a “Memberships” claim? :slight_smile: I can probably fetch this through the Admin API from the downstream service but having them in the JWT would be awesome!

Hey @elijahsgh :wave: Got a fix prepped for this issue here.

Wanted to note that based on a quick glance into the python lib to process signing keys, it looks like it wasn’t able to find the correct key as the use: "sig" was missing from the payload. I’ve added both the use property in the response body and the kid header to make sure more clients have easy time working with the endpoint. Can you please verify it works for you?

Hi naz!
Unfortunately this is not a solution. The modifications in the link alter /ghost/.well-known/jwks.json but I was specifically working with /members/.well-known/jwks.json. I did try your branch and could verify that the changes altered the ghost path jwks.json.

The core problem is the actual JWT (not the JWKS) is missing the kid.

I made a PR here: Fix kid missing from JWT header by elijahsgh · Pull Request #401 · TryGhost/Members · GitHub
I don’t expect that to be merged. It’s just an example :slight_smile:

pyjwt test after the change above:

>>> jwks_client = jwt.PyJWKClient('http://localhost:2368/members/.well-known/jwks.json')
>>> jwks_client.get_signing_key_from_jwt(current_jwt).key_id
'ygdnhQI-mtXEYF3MuMYto_sIDsBtU8bFdEFr6Sza6ss'
1 Like

Hey hey I saw this today! :slight_smile:

Thanks @naz :partying_face:

Any predictions on when this might be in either Ghost 4 or 5? I was thinking of upgrading to Ghost 5 today… can I/should I override the members version during ghost install?

Yeah, the change has landed in main just now :stuck_out_tongue_winking_eye: Yet another reason to migrate to 5.x!

I think ghost install, if you are on the latest ghost-cli version, should be installing 5.0 by default. We have a bug if it’s not!

1 Like