Tell the client how long the Mercure authorization cookie lives - #2012
Tell the client how long the Mercure authorization cookie lives#2012fashxp wants to merge 2 commits into
Conversation
The hub authorises a subscription only at connect time, so a client that cannot know when its cookie expires has no way to renew before it does. mercure/auth now returns the configured lifetime next to the cookie it sets. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Exposes the Mercure authorization cookie lifetime so clients can renew before expiry.
Changes:
- Adds an authorization response DTO and OpenAPI schema.
- Returns the configured cookie lifetime from
/mercure/auth. - Adds HubService lifetime and expiry tests.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/Mercure/Controller/JwtController.php |
Returns lifetime JSON with the cookie. |
src/Mercure/Schema/Authorization.php |
Defines the response DTO. |
src/Mercure/Service/HubService.php |
Exposes the configured lifetime. |
src/Mercure/Service/HubServiceInterface.php |
Adds the lifetime contract. |
tests/Unit/Mercure/Service/HubServiceTest.php |
Tests lifetime and cookie expiry. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| public function getCookieLifetime(): int | ||
| { | ||
| return $this->cookieLifetime; |
There was a problem hiding this comment.
Correct, and verified: with the factory given only the secret, jwtLifetime resolves to session.cookie_lifetime or 3600, entirely independently of mercure_settings.cookie_lifetime. Setting cookie_lifetime: 7200 produced a cookie stamped +7200s around a token expiring at +3600s, so the endpoint would have advertised 7200 and the client would have renewed 36 minutes after the hub stopped accepting the token - the exact dead-authorization window this PR is meant to remove.
Fixed in 78ad8ab. ClientTokenService now takes the configured lifetime and passes an explicit exp claim, which LcobucciFactory::create() honours over its own default:
[$exp => new DateTimeImmutable($"+{$this->cookieLifetime} seconds")]Deliberately not wired into the shared TokenFactoryInterface service: ServerTokenService uses the same factory for the publisher token, and a short cookie_lifetime would then shorten that one too - PublishService caches its Hub with a StaticTokenProvider, so a long-running messenger worker would start publishing with an expired token.
The regression test now decodes the JWT out of the cookie and asserts exp, the cookie expiry and the advertised lifetime all agree ("testAdvertisedLifetimeMatchesTheTokenExpiry"). Confirmed it fails without the fix: "Failed asserting that 3600.2 matches expected 7200".
The factory derived `exp` from `session.cookie_lifetime` (or 3600) instead, so a configured `cookie_lifetime` above that told the client to renew long after the hub had stopped accepting its token. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|



Part of the fix for pimcore/platform-version#346.
Problem
The Mercure hub authorises a subscription exactly once, when the
EventSourceconnects, from themercureAuthorizationcookie sent with that request. The cookie livescookie_lifetimeseconds(3600 by default) while a Studio tab lives for as long as the user leaves it open, and the hub
closes every stream on its own
write_timeout(600s by default) so the browser reconnects roughlyevery nine minutes. Any reconnect made after the cookie expired is accepted as an anonymous
subscription by a hub that allows those, and then receives no private update ever again: no error,
no
onerror, and the client keeps reporting a healthy stream.The client has to renew the cookie before it expires, and today it has no way to know when that is.
Change
POST /mercure/authnow returns the configured lifetime next to the cookie it sets:{ "cookieLifetime": 3600 }Mercure\Schema\Authorizationresponse DTO (OpenAPI documented asMercureAuthorization)HubServiceInterface::getCookieLifetime()exposes the configuredpimcore_studio_backend.mercure_settings.cookie_lifetimeJwtController::auth()returns that DTO as the response bodyReturning the lifetime rather than an absolute expiry keeps it immune to clock differences
between server and browser: the value is relative to the response the client just received.
Backward compatibility
Additive only. The action's return type stays
Response(JsonResponseis one), the status codeand the
Set-Cookieheader are unchanged, and a client that ignores the body behaves exactly asbefore.
HubServiceInterfaceis@internal.The consuming side is pimcore/studio-ui-bundle#4009, which reads the field defensively and falls
back to a one hour assumption, so the two can be merged in either order.
Verification
HubServiceTest(3 cases), including a regression test that the advertised lifetime and theactual cookie expiry are derived from the same value: if they drift, a client that renews "in
time" still reconnects with a dead cookie, which is precisely the failure this is meant to end
vendor/bin/codecept run Unit tests/Unit/Mercuregreen (15 tests)src/Mercure{"cookieLifetime":3600}