Skip to content

Conversation

ghostzero
Copy link

Add support for kid header in JWTs

Hi everyone! 👋

This is my first contribution to the Laravel ecosystem, and I’m excited to propose a feature that I believe will be helpful for many developers.

Motivation

This PR adds support for the kid header as defined in RFC 7515, section 4.1.4.

By including the kid header, third-party applications can leverage JWKS (JSON Web Key Sets) published by the standard discovery endpoint (/.well-known/oauth-authorization-server) to validate JWTs issued by Passport.

Benefits

  • Key rotation support: Applications can seamlessly validate tokens signed with both old and new keys during a rotation.
  • Better interoperability: Third-party services can consume JWKS endpoints, ensuring more standardized JWT verification.

How the kid is generated

The kid is automatically derived from the public key using the JWK Thumbprint (RFC 7638) standard:

  • The RSA key parameters (n, e) are extracted.
  • A minimal JWK is built and hashed with SHA-256.
  • The result is base64url-encoded to form the kid.

This means every public key always produces the same kid. When you rotate keys, the new one will get a different kid, allowing clients to select the right key from your JWKS automatically.

Example

An OAuth discovery document may expose a JWKS URI:

{
  "jwks_uri": "https://example.com/.well-known/jwks.json"
}

Fetching https://example.com/.well-known/jwks.json could return something like:

{
  "keys": [
    {
      "kty": "RSA",
      "n": "...",
      "e": "AQAB",
      "alg": "RS256",
      "kid": "...",
      "use": "sig"
    }
  ]
}

A third-party application can then use these keys to verify tokens issued by Passport:

$keys = JWK::parseKeySet(Cache::remember('jwks', Carbon::now()->addHour(), function () {
    $resp = file_get_contents('https://example.com/.well-known/jwks.json');
    return json_decode($resp, true);
}));

$decoded = (new JWT())->decode($request->bearerToken(), $keys);

Open Questions

  • Would getPublicKeyMaterial() be better placed elsewhere in the codebase?
  • Should Passport middlewares also include built-in JWKS checks to benefit directly from this functionality?

Copy link

Thanks for submitting a PR!

Note that draft PR's are not reviewed. If you would like a review, please mark your pull request as ready for review in the GitHub user interface.

Pull requests that are abandoned in draft may be closed due to inactivity.

@ghostzero ghostzero marked this pull request as ready for review September 25, 2025 21:45
@hafezdivandari
Copy link
Contributor

hafezdivandari commented Sep 25, 2025

To be able to add custom claims/headers, you may refer to thephpleague/oauth2-server#1328

@taylorotwell taylorotwell marked this pull request as draft September 26, 2025 19:53
@ghostzero
Copy link
Author

ghostzero commented Sep 28, 2025

Thanks! I just looked up that PR 😅🙈😂 I really like that idea to be able to modify the token, but that PR is open for 3y+ 🫣

@hafezdivandari
Copy link
Contributor

I know! It's unbelievable to wait 3 years for 4 lines of changes to be merged but here we are!

However, this should be implemented on thephpleague/oauth2-server instead of Passport IMHO.

You may still apply these changes by extending the Laravel\Passport\Bridge\AccessToken class and instruct Passport to use your custom class:

use Laravel\Passport\Passport;

Passport::useAccessTokenEntity(\Laravel\App\Classes\AccessToken::class);
namespace Laravel\App\Classes;

use Laravel\Passport\Bridge\AccessToken as BaseAccessToken;
 
class AccessToken extends BaseAccessToken
{
    // ...
}

@Sephster
Copy link
Contributor

As maintainer, I've prioritsed other PRs but can bump this if you like? I am mid review of token revocation as requested by @hafezdivandari which is a chunky PR. As I've stated many times, maintaining the server isn't easy because we have to ensure that each PR doesn't break with the many RFCs that an OAuth2 server must adhere to so what might on the surface seem like a small change, requires a lot of checking and cross referencing on my part to try to maintain stability in the library, something of which I'm very proud.

Since the PR was published, we've modified pretty much every file in the library, adding over 6000 new lines of code, introducing a new strict type system and a new grant.

I'd love to have merged this in sooner but being a volunteer led project, I have to prioritise my time where it is most effective. I see value in the PR so didn't want to close it as it will be merged at some point. Thanks both for your contributions.

@hafezdivandari
Copy link
Contributor

hafezdivandari commented Sep 29, 2025

Thank you @Sephster for all your work on the server, I’m sure the community deeply appreciates it. Opening issues and PRs is, in my opinion, a clear sign of that.

I’ll resolve the conflicts on thephpleague/oauth2-server#1328 once thephpleague/oauth2-server#1473 has been merged.

PS: The firebase/jwt package, which is widely used in Laravel packages, supports the kid header when encoding/decoding JWTs. The lcobucci/jwt package used in the server doesn’t support this yet. Switching is straightforward and could be implemented later, though it’s a lower priority since there’s already a workaround in place as I mentioned above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants