Skip to content

Profiler: KeyCollector instantiates every configured key on each request, so one unloadable key breaks all pages in dev #738

Closed

Description

@jdreesen

Version(s) affected

4.2.3 (web-token/jwt-bundle), Symfony 7.4, PHP 8.4, with symfony/web-profiler-bundle in dev

Description

With the profiler enabled (kernel.debug and WebProfilerBundle), KeyCollectorCompilerPass adds every service tagged jose.jwk / jose.jwkset to KeyCollector as a method call with a hard reference (addJWK($id, $jwk), addJWKSet($id, $jwkset)). So whenever the profiler is created, i.e. on every request in dev, all configured keys and key sets are instantiated, whether the request uses them or not.

As a result, a key that can't be loaded makes every page fail in dev, although the application itself would only hit it when the key is actually used. A debug tool makes the application stricter than it is without it.

The same eager instantiation means that key sets loaded from a URL (the jku / x5u sources, JKUFactory::loadFromUrl()) are fetched on every request in dev.

How to reproduce

An optional key from an environment variable that is empty in dev:

// config/packages/jose.php
return App::config([
    'jose' => [
        'keys' => [
            'chat_token' => [
                'jwk' => ['value' => '%env(CHAT_TOKEN_PRIVATE_KEY)%'], // empty in dev
            ],
        ],
    ],
]);

With WebProfilerBundle enabled in dev, every request ends with a 500:

JsonException: Syntax error
#0 vendor/web-token/jwt-library/KeyManagement/JWKFactory.php(159): json_decode('', true, 512, 4194304)
#1 var/cache/dev/.../KernelDevDebugContainer.php: JWKFactory::createFromJsonObject('')
#2 var/cache/dev/.../KernelDevDebugContainer.php: getJose_Key_ChatTokenService()
#3 var/cache/dev/.../KernelDevDebugContainer.php: getJoseCollectorService()
#4 var/cache/dev/.../KernelDevDebugContainer.php: get_Container_Private_ProfilerService()
#5 var/cache/dev/.../KernelDevDebugContainer.php: getProfilerListenerService()

Without the profiler (or in prod), the application works: the key is only created when a token is signed, and the code checks for an empty value before.

Possible Solution

  • Pass the keys lazily to KeyCollector, e.g. a service locator (ServiceLocatorTagPass::register()) or service closures, instead of addJWK() / addJWKSet() calls with hard references.
  • Resolve them in collect(), catching Throwable per key and showing the error in the panel instead of failing the request.

Additional Context

Workaround: define the key as a plain service (JWKFactory::createFromJsonObject() as factory) instead of under jose.keys, so it isn't tagged and the collector doesn't see it.

Related: #502 asked for a way to disable the data collectors in dev; this is about the key collector's eager loading, which would make such an option less necessary.

Activity

  1. added this to the 4.2.4 milestone on Oct 30, 2026
  2. added a commit that references this issue on Oct 30, 2026
    9aadf13
  3. Spomky commented on Oct 30, 2026

    @Spomky
    Member

    Many thanks.
    This is addressed by #739 and will be tagged very soon.
    Kind regards.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions