clerk-js 6 token cache ages session tokens by server `iat` against the browser clock — slow client clocks resend expired JWTs on every request
Les mainteneurs répondent en général sous 1 jour
Personne n'a encore pris cette issue.
Évaluation
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Accessibilité débutants
- 18/100
- Type d'issue
- Bug
- Clarté
- Plutôt claire
- Activité
- Active
- Stack technique
- javascript, nextjs, typescript
- Domaine
- authentication, frontend
Piste de recherche
The cache logic lives in packages/clerk-js/src/core/tokenCache.ts: start with setInternal, where createdAt is set from the JWT iat, and get(), where eviction compares against Date.now() and POLLER_INTERVAL_IN_MS. Reproduce by shifting Date.now back 120 s in a Playwright page and checking whether a backend stub rejects tokens past their exp. Done means evicting on receipt-time or offset-corrected time so the stub sees no expired tokens, with the refresh timer also corrected.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Description
Preliminary checks
- I have searched existing issues: no report mentions the token cache's clock dependence ("clock skew", "iat", "token cache").
Package + version
@clerk/clerk-js 6.39.0 (loaded from the CDN by @clerk/nextjs 7.9.12 / Core 3). Verified against the 6.39.0 and main sources and compared with @clerk/clerk-js 5.128.0.
Description
packages/clerk-js/src/core/tokenCache.ts (6.x) ages a cached session token from the server-issued iat against the browser clock:
- after a token resolves,
setInternalsetslive.createdAt = issuedAt(the JWTiat) and computesremainingTtl = exp − Math.floor(Date.now()/1000)for the eviction timer; get()evicts only when(expiresIn − (now − createdAt)) <= POLLER_INTERVAL_IN_MS/1000(5 s), wherenowisDate.now()andcreatedAt/expiresIncome from the token.
Every term except now is server time, so the eviction point moves with the browser's clock error. In 5.x the same cache stamped a locally fetched token with nowSeconds on arrival (createdAt = entry.createdAt ?? nowSeconds, expiresIn = exp − iat) and evicted 15 s early, so a wrong browser clock did not matter: the token was dropped ~45 s after the browser received it regardless of skew.
Consequence on 6.x. A browser whose clock is S seconds slow believes a 60 s token has 60 + S seconds of life. getToken() keeps returning it, with no error, until 55 + S seconds after issue. A backend that validates exp with the usual 30 s leeway rejects it from 90 s on, so for S > 35 s every API call from that browser fails for (S − 35) seconds out of every (55 + S) — on a clock 5 minutes slow, about three quarters of all requests. The proactive refresh timer (refreshFireTime = remainingTtl − leeway − 2) is scheduled from the same skewed remainingTtl, so it fires too late as well. Fast clocks only cause early refreshes.
What we observed (production, after upgrading @clerk/nextjs 6.39 → 7.9, i.e. CDN clerk-js 5 → 6)
- a few long-lived sessions sending an expired token on every background poll for 30–60 min, then recovering when the cache finally evicted;
- user-initiated PATCH/DELETE calls answered 401 for 3–13 minutes straight on those sessions;
- backend log flooded with
JWT expired: Signature has expired(up to ~2,500 rows / 4 h);getToken()never threw on the client, so no frontend error was logged; - no Clerk-side incident: fresh tokens validated fine fleet-wide the whole time.
Reproduction
Playwright, Clerk dev instance, a signed-in page whose Date.now is shifted 120 s into the past by an init script, polling an API stub that decodes the bearer exp and answers 401 when the real clock is more than 30 s past it:
== clerk-js 6.39.0, no app-side workaround: skew=120s, 22 backend calls over 290s ==
window 200 401 tokens
30-60 3 0 …kt0zl9VA
60-90 1 0 …kt0zl9VA
90-120 2 0 …kt0zl9VA
120-150 0 2 …kt0zl9VA
150-180 0 4 …kt0zl9VA
180-210 1 0 …Z7olUPNw
210-240 2 0 …Z7olUPNw
240-270 1 0 …Z7olUPNw
270-300 0 4 …Z7olUPNw
300-330 0 2 …Z7olUPNw
total: 22 calls, 12 rejected (55%), 2 distinct tokens
== same, with our skipCache-on-rejected workaround: skew=120s, 19 backend calls over 290s ==
window 200 401 tokens
30-60 3 0 …MbqVOcNw
60-90 1 0 …MbqVOcNw
90-120 2 0 …MbqVOcNw
120-150 1 1 …MbqVOcNw …AFLN8Tyw
150-180 2 0 …AFLN8Tyw
180-210 1 0 …AFLN8Tyw
210-240 2 1 …AFLN8Tyw …jgxTSUHQ
240-270 1 0 …jgxTSUHQ
270-300 2 0 …jgxTSUHQ
300-330 1 1 …jgxTSUHQ …__qXlmuQ
total: 19 calls, 3 rejected (16%), 4 distinct tokens
(the API stub answers 401 once a token is >90 s old by the real clock; without the workaround every poll AND its retry carry the same stale token until clerk-js evicts it ~175 s after issue)
Expected behavior
Freshness measured on the browser clock from receipt (as 5.x did), or createdAt corrected by the observed server/client offset (iat vs Date.now() at receipt, or the Date header of the /tokens response).
Workaround we shipped
Record the bearer a backend 401 refused and have our getToken wrapper call getToken({ skipCache: true }) when the cache hands that same string back (one shared in-flight mint). Reactive, not a fix.
- Langage dominant
- TypeScript
- Étoiles
- 1.8k
- Forks
- 480
- Merge moyen
- 1 j 19 h
- PR mergées (30 j)
- 285
Préparer son environnement
- Aucun Dockerfile ni fichier Docker Compose
- Propose un modèle de pull request
- Lire le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Autres issues de clerk/javascript
-
Difficulté 2/5 1-3 heures Accessibilité débutants 78/100
clerk/javascript#10026 · 2 commentaires ·
Les mainteneurs répondent en général sous 1 jour
-
@clerk/nextjs: onBeforeSetActive never settles when invalidateCacheAction rejects (e.g. after a redeploy), so setActive and signOut hang foreverPeut-être pris @RaphaelFakhri l’a pris il y a 13 jours. Ouverte
Difficulté 2/5 1-3 heures Accessibilité débutants 78/100
clerk/javascript#9987 · 1 commentaire ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 3/5 1-2 jours Accessibilité débutants 72/100
clerk/javascript#10285 ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 4/5 3-5 jours Accessibilité débutants 25/100
clerk/javascript#10185 · 1 réaction ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 4/5 1-2 jours Accessibilité débutants 45/100
clerk/javascript#10118 ·
Les mainteneurs répondent en général sous 1 jour
Toutes les issues de clerk/javascript
Issues similaires
-
Difficulté 2/5 1-3 heures Accessibilité débutants 72/100
simkimsia/netlify-axi#19 ·
Les mainteneurs répondent en général sous 1 jour
-
Difficulté 2/5 1-3 heures Accessibilité débutants 72/100
subhranshudash13-dotcom/Contribo#74 ·
Les mainteneurs répondent en général sous 1 jour
-
enhancement frontend good first issue Stellar Wave trivial
Difficulté 2/5 1-3 heures Accessibilité débutants 76/100
Godgift-care/sci-healthcare-frontend#166 · 1 commentaire ·
Les mainteneurs répondent en général sous 1 jour
-
backend good first issue security Stellar Wave trivial
Difficulté 2/5 1-3 heures Accessibilité débutants 82/100
Godgift-care/sci-healthcare-backend#171 · 1 commentaire ·
Les mainteneurs répondent en général sous 1 jour