feat(js): attach an optional server-configured session token to sign-in - #9299
Draft
zourzouvillys wants to merge 3 commits into
Draft
feat(js): attach an optional server-configured session token to sign-in#9299zourzouvillys wants to merge 3 commits into
zourzouvillys wants to merge 3 commits into
Conversation
Mints an opaque, random correlation id and acquires a signed Protect session token once per browser session, shared across tabs under a lock. The token, the correlation id and an acquisition status travel in the form-encoded body of sign-in and sign-up POSTs. Acquisition never blocks a sign-in: on timeout, script-load failure or a non-2xx response a structured status travels in the token's place and the request proceeds. Instances whose loader config does not reference the new placeholders keep today's behaviour and store nothing in the browser.
🦋 Changeset detectedLatest commit: 7a76ac2 The changes in this PR will be included in the next version bump. This PR includes changesets to release 23 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
@clerk/astro
@clerk/backend
@clerk/chrome-extension
@clerk/clerk-js
@clerk/electron
@clerk/electron-passkeys
@clerk/eslint-plugin
@clerk/expo
@clerk/expo-google-signin
@clerk/expo-passkeys
@clerk/express
@clerk/fastify
@clerk/hono
@clerk/localizations
@clerk/nextjs
@clerk/nuxt
@clerk/react
@clerk/react-router
@clerk/shared
@clerk/tanstack-react-start
@clerk/testing
@clerk/ui
@clerk/upgrade
@clerk/vue
commit: |
Follows the EDR-0020 amendment (clerk/protect#408): the session token now ships inline on the loader's own global rather than behind a second request, so the gate costs no extra round trip on the auth critical path. `tokenUrl` becomes an explicit opt-in for the upgrade mint, and is the only path that can report `fetch_error` or an HTTP status. That removes the derive-the-endpoint-from-an-attribute fallback, which resolved against `document.baseURI` (pointing polling at the app's own origin) and stripped a trailing `{cid}` via relative resolution. The session now owns the token loader: it injects it under the acquisition lock and reads the token when the element fires `load`. Every other loader is applied on every page load, rather than all loaders being suppressed whenever a token happened to be cached. Also fixed: - A malformed loader entry no longer escapes `Protect.load()` and fails `Clerk.load()` for every visitor. - `readStored` falls back to the in-memory store, so a token written there under an exhausted quota is no longer read back as absent. - `getRequestParams` is bounded by the acquisition deadline, and a server-supplied `tokenTimeoutMs` is capped, so a sign-in cannot be stalled before dispatch. It can no longer reject into the request either. - A settled, tokenless acquisition re-arms after a cooldown instead of being replayed for the life of the tab. - The token store and lock are namespaced per instance, so two instances on one origin no longer share a token. - A stored token is validated against a maximum lifetime and length, so a planted entry cannot suppress acquisition indefinitely. - The upgrade mint retries a transient 408/429/5xx within its deadline. - `{instance_id}` alone no longer mints and persists a client id. - `textContent` placeholders are detected and substituted. - `isMergeableBody` admits only plain objects, so a Blob or array body is no longer spread away.
Aligns with clerk/protect: the loader global carries `ready`, a promise
resolving to `{token, exp}` or `{status: "no_token"}`, rather than the
token directly. It is the loader's completion signal and the seam where
page-side probes and the EDR-0020 proof-of-possession challenge will
live, so the token is awaited (raced against the acquisition deadline)
instead of read synchronously.
`{sdkver}` joins the placeholder set, substituted with the build version.
Its presence is what tells the server this build interpolates at all, and
so can be served the current shape; `/v1/environment` is cached and
cannot vary by SDK version, so the negotiation has to happen per request.
A build that leaves it verbatim is served the base `{v, id}` shape.
`no_token` joins the status set, matching Warden's allowlist. It is what
the base shape, a load with no correlation id, and a failed mint all
report. Reporting `timeout` for those would mark every load between this
shipping and the server half deploying as a failure, making a normal
rollout look like an outage.
A `ready` that rejects is treated as nothing served rather than
propagated; the contract says it never rejects, but an unhandled
rejection on every page load is not worth taking on trust.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds an optional, server-configured session token to
@clerk/clerk-js, attached to sign-in and sign-up requests.It is inert unless an instance's loader configuration opts into it. An instance not using it keeps today's behaviour exactly, stores nothing in the browser, and sends no additional parameters.
{cid},{pid},{rid},{instance_id},{sdkver}— substituted into a loader's attribute values andtextContentbefore the element is appended. An unrecognised{…}is left verbatim, so an older SDK loading the same configuration stays compatible.crypto.getRandomValuesas lowercase unpadded RFC 4648 base32. Nothing is derived from the device — no fingerprinting input, no clock, no user data.SafeLockprimitive (core/auth/safeLock.ts) thatSessionCookiePolleralready uses, with a re-check inside the lock so concurrent tabs do not each acquire in turn. A wedged leader delays nobody, and a leader tab closing mid-run releases its lock automatically.Clerk.load()rather than at sign-in, so acquisition stays off the latency-critical path. It is bounded by a deadline and can never block or fail a sign-in: when no token is obtained, a status travels in its place and the request proceeds unchanged._methodinto the query. No request header is added anywhere. The merge is an explicit step infapiClient.requeston a path allowlist, because theonBeforeRequestcallbacks fire after the body is stringified.ProtectLoadergains two optional fields,tokenUrlandtokenTimeoutMs, both inert unless set.rolloutmoved out ofapplyLoaderinto a module-level helper soload()can decide participation before it starts applying loaders. Same semantics, evaluated once.Bundle thresholds were bumped on the affected bundles to cover the addition.
Checklist
pnpm testruns as expected.pnpm buildruns as expected.Type of change