Skip to content

fix: improve auth session sync reliability#24

Merged
TimChild merged 3 commits into
mainfrom
pr-22-takeover
Apr 25, 2026
Merged

fix: improve auth session sync reliability#24
TimChild merged 3 commits into
mainfrom
pr-22-takeover

Conversation

@TimChild

@TimChild TimChild commented Apr 25, 2026

Copy link
Copy Markdown
Owner

Takeover of #22 by @PJ-Snap with review fixes applied.

Summary

  • Add configurable JWT validation leeway (default 60s) to handle clock drift between Clerk servers and the backend
  • Catch ExpiredTokenError alongside InvalidClaimError / MissingClaimError to prevent crashes on expired tokens
  • Rewrite ClerkSessionSynchronizer JS for reliability:
    • Use useRef to deduplicate rapid calls while remaining reconnect-safe
    • Request fresh tokens with skipCache to avoid near-expiry cached tokens
    • Handle token retrieval failures gracefully
    • Include all dependencies in useEffect array
  • Add unit tests for expired token handling and JS code correctness
  • Add pythonpath config for pytest to find custom_components

Review feedback addressed (Copilot review on #22)

Dedupe + retry fixes in clerk_provider.py:

  • Only update lastSentRef after a confirmed dispatch so a failed token fetch doesn't poison the dedupe and block later retries.
  • Added inFlightRef to prevent overlapping getToken calls when the effect re-fires before the in-flight promise resolves.
  • Retry getToken once after a 500 ms delay before clearing the backend session, so transient token-fetch failures don't force a backend logout while Clerk is still signed in.
  • On final failure, leave lastSentRef unchanged so the next trigger (reconnect, sign-in toggle, etc.) re-attempts the sync.

Test typecheck fixes:

  • # pyright: ignore[reportCallIssue] for the _reflex_internal_init Reflex internal init flag.
  • # pyright: ignore[reportAttributeAccessIssue] for accessing .fn on the wrapped EventCallback.

Test plan

  • uv run ruff check . passes
  • uv run pyright passes (0 errors)
  • uv run pytest tests/test_clerk_provider_unit.py -v passes (2/2)
  • CI green on push

Supersedes #22. Original work by @PJ-Snap.

PJ-Snap and others added 2 commits April 7, 2026 14:13
- Add configurable JWT validation leeway (default 60s) to handle clock
  drift between Clerk servers and the backend
- Catch ExpiredTokenError alongside InvalidClaimError/MissingClaimError
  to prevent crashes on expired tokens
- Rewrite ClerkSessionSynchronizer JS for reliability:
  - Use useRef to deduplicate rapid calls while remaining reconnect-safe
  - Request fresh tokens with skipCache to avoid near-expiry cached tokens
  - Handle token retrieval failures gracefully (clear session instead of hang)
  - Include all dependencies in useEffect array ([isLoaded, isSignedIn, addEvents, getToken])
- Add unit tests for expired token handling and JS code correctness
- Add pythonpath config for pytest to find custom_components
JS dedupe + retry fixes (Copilot review):
- Only update lastSentRef after a confirmed dispatch so a failed token fetch
  doesn't poison the dedupe and block later retries.
- Add inFlightRef to prevent overlapping getToken calls when the effect
  re-fires before the in-flight promise resolves.
- Retry getToken once after a 500ms delay before clearing the backend
  session, so transient token-fetch failures don't force a backend logout
  while Clerk is still signed in.
- On final failure, leave lastSentRef unchanged so the next trigger
  (reconnect, sign-in toggle) re-attempts the sync.

Test typecheck fixes:
- pyright ignore for the `_reflex_internal_init` Reflex internal init flag.
- pyright ignore for accessing `.fn` on the wrapped EventCallback.

Co-Authored-By: Paul Johnson <paul.johnson@snaplabs.ai>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves Clerk auth session synchronization reliability between the Reflex frontend and backend by hardening JWT validation and making the frontend synchronizer more reconnect- and failure-tolerant.

Changes:

  • Add configurable JWT validation leeway (default 60s) and handle ExpiredTokenError during claim validation.
  • Rewrite ClerkSessionSynchronizer JS to dedupe rapid triggers, request fresh tokens (skipCache), and handle token-fetch failures with retry/clear behavior.
  • Add unit tests for expired-token handling and JS regression checks; configure pytest pythonpath for local imports.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
custom_components/reflex_clerk_api/clerk_provider.py Adds JWT leeway + expired-token handling; rewrites session sync JS logic and React imports.
tests/test_clerk_provider_unit.py Adds regression/unit tests for leeway + expired-token path and generated JS content.
pyproject.toml Adds pytest pythonpath to ensure custom_components is importable in tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +424 to +430
const stateKey = isSignedIn ? "signed_in" : "signed_out"
if (
lastSentRef.current?.stateKey === stateKey &&
lastSentRef.current?.addEvents === addEvents
) return
if (inFlightRef.current) return

Comment on lines +447 to +450
.then(token => {{
if (token) {{
addEvents([ReflexEvent("{state}.set_clerk_session", {{token}})])
lastSentRef.current = {{ stateKey, addEvents }}
- Drop inFlightRef hard gate. It blocked the !isSignedIn branch, so a
  sign-out occurring during an in-flight token fetch would not dispatch
  clear_clerk_session and the backend session would stay stale until
  another trigger arrived.
- Replace it with a requestIdRef counter that's incremented on every
  effect run with a new desired state. The in-flight fetch's then/catch
  handlers check the captured myRequestId against requestIdRef.current
  before dispatching - so a getToken() that resolves after sign-out can
  no longer dispatch a stale set_clerk_session.
- Sign-out path now runs unconditionally and immediately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@TimChild

Copy link
Copy Markdown
Owner Author

Addressed both Copilot race-condition comments in the new commit:

Comment 1 (sign-out blocked by inFlightRef) + Comment 2 (stale set_clerk_session after sign-out) — both stemmed from inFlightRef being a hard gate. Dropped it entirely and replaced with a requestIdRef counter:

  • Every effect run with a new desired state increments requestIdRef.current and captures it as myRequestId.
  • The sign-out branch now runs unconditionally and immediately.
  • The token-fetch promise handlers compare myRequestId === requestIdRef.current before dispatching — so a getToken() that resolves after sign-out (or after any newer effect run) silently drops instead of dispatching a stale set_clerk_session.

This fixes both races: sign-out can no longer be suppressed by an in-flight fetch, and a stale token-fetch result can no longer re-authenticate the backend after sign-out.

@TimChild TimChild merged commit 5d32e74 into main Apr 25, 2026
7 checks passed
@TimChild TimChild deleted the pr-22-takeover branch April 25, 2026 17:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants