The deprecated key set cache of JKUFactory and X5UFactory ignores the request headers

Open
#763 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
65/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
php, symfony
Domain
backend, security

Research direction

Start with UrlKeySetFactoryTrait::getContent() for 4.3 and UrlKeySetFactory::getContent() for 3.4 through 4.2, as named in the issue. Trace how the URL and request headers are used to build the cache key, then check the existing tests for these factories. Done means cached results for the same URL and different headers are kept separate; the issue also calls for an upgrade note about Vary when replacing this cache with Symfony's CachingHttpClient.

Written by the indexing model from the issue text.

Description

bug
Version(s) affected

3.4.0 to 3.4.12, 4.0.0 to 4.0.8, 4.1.0 to 4.1.10, 4.2.0 to 4.2.5, 4.3.x-dev

Description

With enabledCache(), JKUFactory and X5UFactory store the response under hash('xxh128', $url), although the request also carries the $header argument of loadFromUrl(). A later call for the same URL with other headers gets the document fetched for the first ones, without any request, until the entry expires (3600 s by default). With a pool shared between requests or processes (filesystem, APCu, Redis, a long-running worker), this applies to the whole application.

When an endpoint answers with a different key set per header value (a tenant header, a per-tenant Authorization), the verifier of one tenant receives the keys of another. A genuine token of tenant A then verifies for tenant B, unless an iss/aud check binds tokens to their tenant, and tenant B's own tokens fail until the entry expires.

The bundle never calls enabledCache(). The cache is deprecated since 4.1 (runtime notice from 4.3 on) and goes away in 5.0, but 3.4.x and 4.0.x still present it as a normal feature.

How to reproduce
use Jose\Component\KeyManagement\JKUFactory;
use Symfony\Component\Cache\Adapter\ArrayAdapter;

$factory = new JKUFactory($client); // the endpoint answers with one key set per "X-Tenant" value
$factory->enabledCache(new ArrayAdapter());

$acme = $factory->loadFromUrl('https://keys.example.com/jwks', ['X-Tenant' => 'acme']);
$globex = $factory->loadFromUrl('https://keys.example.com/jwks', ['X-Tenant' => 'globex']);

// $globex holds acme's keys, and the endpoint received a single request.
Possible Solution

Include the headers in the cache key, for example hash('xxh128', serialize([$url, $header])), in UrlKeySetFactoryTrait::getContent() (4.3) and UrlKeySetFactory::getContent() (3.4.x to 4.2.x). Entries stored by an older version are simply no longer read.

Additional Context

Found by the Anthropic OSS Scanner (ANT-2026-QTH1FPG2).

The replacement named by the deprecation, Symfony's CachingHttpClient, keys its entries on the method, the URL, a few standard headers (Authorization, Cookie, Accept*, Host, User-Agent…) and the headers listed in the response's Vary. A key service that selects the document with a custom header must send Vary for that header, or the same mixing happens. Worth a line in the upgrade notes.

Dominant language
PHP
Stars
954
Forks
119
Avg merge
48m
Merged PRs (30d)
17

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from web-token/jwt-framework

All issues in web-token/jwt-framework

Similar issues

More PHP issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.