Repository navigation
Measure SessionTokenCache age on the device clock #10361
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@clerk/clerk-js': patch | ||
| --- | ||
|
|
||
| SessionTokenCache measures token age on the device clock, so a slow client clock no longer keeps serving a session token after it expired on the server. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -292,7 +292,6 @@ const MemoryTokenCache = (prefix?: string): TokenCache => { | |
|
|
||
| setInternal( | ||
| { | ||
| createdAt: iat, | ||
| tokenId: data.tokenId, | ||
| tokenResolver: Promise.resolve(token), | ||
| }, | ||
|
|
@@ -328,6 +327,7 @@ const MemoryTokenCache = (prefix?: string): TokenCache => { | |
|
|
||
| const nowSeconds = Math.floor(Date.now() / 1000); | ||
| const createdAt = entry.createdAt ?? nowSeconds; | ||
| entry.createdAt = createdAt; | ||
| const value: TokenCacheValue = { createdAt, entry, expiresIn: undefined }; | ||
|
|
||
| // Chain the freshest known token across replacements. This never touches | ||
|
|
@@ -402,13 +402,12 @@ const MemoryTokenCache = (prefix?: string): TokenCache => { | |
| const expiresAt = winnerClaims.exp; | ||
| const issuedAt = winnerClaims.iat; | ||
| const expiresIn: Seconds = expiresAt - issuedAt; | ||
| // Timers run relative to now, while createdAt/expiresIn describe the token's | ||
| // real validity window for get(). An aged winner (alive for part of its | ||
| // lifetime already) must be evicted and refreshed by its real expiry, not a | ||
| // full lifetime from now. | ||
| const remainingTtl: Seconds = expiresAt - Math.floor(Date.now() / 1000); | ||
| // Measure elapsed time on the device clock from when we cached the | ||
| // entry (`live.createdAt`), not from JWT `iat`. Mixing server `iat` | ||
| // with `Date.now()` serves expired tokens when the device runs behind. | ||
| const nowSeconds = Math.floor(Date.now() / 1000); | ||
| const remainingTtl: Seconds = expiresIn - (nowSeconds - live.createdAt); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Account for a token’s age before cache insertion. If a token arrives 40 seconds after its 🤖 Prompt for AI Agents |
||
|
|
||
| live.createdAt = issuedAt; | ||
| live.expiresIn = expiresIn; | ||
|
|
||
| if (remainingTtl <= 0) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: clerk/javascript
Length of output: 17239
Do not persist the default cache time on the caller’s entry.
If a caller updates a previously cached entry with a fresh resolver and calls
set(entry)again, this assignment preserves the first insertion time onentry.createdAt. The second call can then expire the fresh token early. Store the default timestamp on an internal entry copy so each reused caller entry receives a new cache time.🤖 Prompt for AI Agents