feat(create): add Descope auth add-on for React#483
Conversation
📝 WalkthroughWalkthroughThis PR adds a new Descope authentication add-on for the React framework generator, including an app-root AuthProvider, a header user menu with sign-out, a ChangesDescope Auth Add-on
Estimated code review effort: 2 (Simple) | ~15 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant DescopeDemo
participant AppDescopeProvider
participant DescopeSDK as Descope Widget/SDK
User->>DescopeDemo: Visit /demo/descope
DescopeDemo->>AppDescopeProvider: useSession()
AppDescopeProvider-->>DescopeDemo: session state (loading/authenticated)
alt Not authenticated
DescopeDemo->>DescopeSDK: render Descope flow (flowId, theme)
User->>DescopeSDK: complete sign-up-or-in flow
DescopeSDK-->>AppDescopeProvider: session established
else Authenticated
DescopeDemo->>DescopeSDK: useUser()
DescopeSDK-->>DescopeDemo: user data (email, picture)
DescopeDemo-->>User: render welcome greeting
end
User->>DescopeSDK: click Sign out (HeaderUser)
DescopeSDK-->>AppDescopeProvider: session cleared
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
packages/create/src/frameworks/react/add-ons/descope/assets/src/routes/demo/descope.tsx (2)
44-55: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDuplicated "Built with DESCOPE" link markup.
The same anchor block appears in both
SignedOut(lines 44-55) andSignedInGreeting(lines 93-104). Extract a small shared component to keep them in sync.♻️ Suggested extraction
+function DescopeAttribution() { + return ( + <p className="demo-muted text-center text-xs"> + Built with{' '} + <a + href="https://descope.com" + target="_blank" + rel="noopener noreferrer" + className="font-medium" + > + DESCOPE + </a> + . + </p> + ) +}Then replace both occurrences with
<DescopeAttribution />(prepending "Sign out from the avatar in the header. " before it inSignedInGreeting).Also applies to: 93-104
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/create/src/frameworks/react/add-ons/descope/assets/src/routes/demo/descope.tsx` around lines 44 - 55, The “Built with DESCOPE” anchor markup is duplicated in both SignedOut and SignedInGreeting, so extract it into a shared DescopeAttribution component in descope.tsx and replace both inline blocks with that component. Keep the shared link markup identical in both places, and in SignedInGreeting prepend the existing “Sign out from the avatar in the header.” text before DescopeAttribution so both views stay consistent.
38-42: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueHardcoded
theme="light"ignores dark mode.The rest of the add-on (header-user, SignedInGreeting) uses
dark:Tailwind variants, indicating the app supports dark mode. The<Descope>widget is fixed totheme="light"and won't adapt. Consider detecting the user's color scheme and passing it dynamically, or at minimum documenting this limitation.// If the SDK supports a "theme" prop that accepts "auto" or a dynamic value: <Descope flowId="sign-up-or-in" theme={document.documentElement.classList.contains('dark') ? 'dark' : 'light'} onError={(err) => console.error('Descope flow error', err)} />🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/create/src/frameworks/react/add-ons/descope/assets/src/routes/demo/descope.tsx` around lines 38 - 42, The Descope widget is hardcoded to a light theme, so it won’t follow the app’s dark-mode support. Update the <Descope> usage in the demo route to pass a dynamic theme value based on the current color scheme (or the SDK’s auto mode if available), and keep the existing onError handling unchanged. Use the <Descope> component and its theme prop as the main touchpoints when making the change.packages/create/src/frameworks/react/add-ons/descope/README.md (1)
26-33: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
RedirectToSignInis illustrative pseudocode.The
RedirectToSignIncomponent in the route-protection example doesn't exist in the codebase. Consider adding a comment noting this is a placeholder the user must implement, or replace it with a TanStack Router redirect for accuracy.- if (!isAuthenticated) return <RedirectToSignIn /> + // Replace with your own redirect, e.g. `navigate({ to: '/demo/descope' })` + if (!isAuthenticated) return <Navigate to="/demo/descope" />🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/create/src/frameworks/react/add-ons/descope/README.md` around lines 26 - 33, The route-protection example in the Descope README uses a non-existent RedirectToSignIn component, so update the example to avoid implying it is available. In the ProtectedPage snippet, either add an inline note that RedirectToSignIn is placeholder pseudocode the user must implement, or replace it with the actual TanStack Router redirect pattern. Keep the guidance tied to ProtectedPage and RedirectToSignIn so readers understand what must be changed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/create/src/frameworks/react/add-ons/descope/assets/src/integrations/descope/header-user.tsx`:
- Around line 24-30: The Sign out click handler in header-user.tsx ignores the
Promise returned by sdk.logout(), so logout failures can become unhandled
rejections. Update the onClick path around sdk.logout() to await it or attach a
catch, and surface a visible fallback or error state from this component so
failures are handled cleanly.
- Around line 8-11: The avatar initial in header-user.tsx is rendered before the
user profile has finished loading, which can show the fallback too early. Update
the HeaderUser component to also guard on isUserLoading alongside
isSessionLoading and isAuthenticated, and only compute/render the initial after
user loading completes so the `useUser()` data is available before using
`user?.name` or `user?.email`.
In `@packages/create/src/frameworks/react/add-ons/descope/README.md`:
- Line 16: The README description for the Descope header user menu is inaccurate
because `header-user.tsx` does not render a “Sign in” link when unauthenticated.
Update the documentation text for the `header-user` integration so it matches
the actual `HeaderUser` behavior: it returns nothing when `!isAuthenticated` and
only shows the avatar and “Sign out” state when signed in.
---
Nitpick comments:
In
`@packages/create/src/frameworks/react/add-ons/descope/assets/src/routes/demo/descope.tsx`:
- Around line 44-55: The “Built with DESCOPE” anchor markup is duplicated in
both SignedOut and SignedInGreeting, so extract it into a shared
DescopeAttribution component in descope.tsx and replace both inline blocks with
that component. Keep the shared link markup identical in both places, and in
SignedInGreeting prepend the existing “Sign out from the avatar in the header.”
text before DescopeAttribution so both views stay consistent.
- Around line 38-42: The Descope widget is hardcoded to a light theme, so it
won’t follow the app’s dark-mode support. Update the <Descope> usage in the demo
route to pass a dynamic theme value based on the current color scheme (or the
SDK’s auto mode if available), and keep the existing onError handling unchanged.
Use the <Descope> component and its theme prop as the main touchpoints when
making the change.
In `@packages/create/src/frameworks/react/add-ons/descope/README.md`:
- Around line 26-33: The route-protection example in the Descope README uses a
non-existent RedirectToSignIn component, so update the example to avoid implying
it is available. In the ProtectedPage snippet, either add an inline note that
RedirectToSignIn is placeholder pseudocode the user must implement, or replace
it with the actual TanStack Router redirect pattern. Keep the guidance tied to
ProtectedPage and RedirectToSignIn so readers understand what must be changed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 6579b9d0-eeb2-449f-9297-ccc8b61e9a68
⛔ Files ignored due to path filters (2)
packages/create/src/frameworks/react/add-ons/descope/logo.svgis excluded by!**/*.svgpackages/create/src/frameworks/react/add-ons/descope/small-logo.svgis excluded by!**/*.svg
📒 Files selected for processing (8)
.changeset/descope-auth-addon.mdpackages/create/src/frameworks/react/add-ons/descope/README.mdpackages/create/src/frameworks/react/add-ons/descope/assets/_dot_env.local.appendpackages/create/src/frameworks/react/add-ons/descope/assets/src/integrations/descope/header-user.tsxpackages/create/src/frameworks/react/add-ons/descope/assets/src/integrations/descope/provider.tsxpackages/create/src/frameworks/react/add-ons/descope/assets/src/routes/demo/descope.tsxpackages/create/src/frameworks/react/add-ons/descope/info.jsonpackages/create/src/frameworks/react/add-ons/descope/package.json
| if (isSessionLoading || !isAuthenticated) return null | ||
|
|
||
| const email = user?.email | ||
| const initial = (user?.name || email || 'U').charAt(0).toUpperCase() |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check the `@descope/react-sdk` type definitions for useUser return shape
fd -t f 'react-sdk' -e d.ts node_modules/@descope 2>/dev/null | head -5
# If not found in node_modules, check the package's exported types
rg -n 'useUser' --type=ts -A5 packages/create/src/frameworks/react/add-ons/descope/ 2>/dev/nullRepository: TanStack/cli
Length of output: 2891
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the local component and any nearby docs or examples for Descope hook usage.
sed -n '1,120p' packages/create/src/frameworks/react/add-ons/descope/assets/src/integrations/descope/header-user.tsx
printf '\n---\n'
sed -n '1,140p' packages/create/src/frameworks/react/add-ons/descope/assets/src/routes/demo/descope.tsx
printf '\n---\n'
rg -n 'isUserLoading|useUser\(|useSession\(' packages/create/src/frameworks/react/add-ons/descope -A3 -B3
# Try to locate installed type definitions or source for `@descope/react-sdk` in the repo/cache.
printf '\n---\n'
fd -a 'react-sdk' . 2>/dev/null | head -50
printf '\n---\n'
fd -a 'descope' . 2>/dev/null | head -50Repository: TanStack/cli
Length of output: 7987
🌐 Web query:
@descope/react-sdk useUser loading state isUserLoading useSession user loading documentation
💡 Result:
In the Descope React SDK, the hooks useSession and useUser are used to manage authentication and user data. Both hooks provide specific boolean flags to track loading states, which are essential for handling UI transitions while the SDK initializes or refreshes authentication status [1][2][3]. Key Loading State Hooks: - useSession: Returns isSessionLoading (a boolean) to indicate whether the session state is currently being fetched or validated [1][2][4]. - useUser: Returns isUserLoading (a boolean) to indicate whether user profile information is currently being fetched [1][3]. Best Practices for Implementation: - Usage within AuthProvider: These hooks must be used within an AuthProvider context [1][2]. If used outside of this context, they will throw an error [1][2]. - Conditional Rendering: You should check both isSessionLoading and isUserLoading before rendering protected content to ensure the application waits for the authentication process to complete [1][2][3]. Example Pattern: const { isAuthenticated, isSessionLoading } = useSession; const { user, isUserLoading } = useUser; if (isSessionLoading || isUserLoading) { return
@descope/react-sdk vs @descope/nextjs-sdk), you may occasionally see different naming conventions like isLoading instead of isSessionLoading in some documentation examples [6], but isSessionLoading and isUserLoading are the standard, widely-documented properties for these specific hooks in current releases [1][2][3].
Citations:
- 1: react-sdk: isUserLoading becomes false before user and is Authenticated are set as expected descope/descope-js#1132
- 2: https://docs.descope.com/getting-started/nextjs/index
- 3: https://github.com/descope/ai/blob/main/rules/client-sdks/descope-nextjs.mdc
- 4: https://docs.descope.com/client-sdk/auth-helpers
- 5: fix(react-sdk): clear loading state when AuthProvider refresh/me reject descope/descope-js#1416
- 6: https://github.com/descope/ai/blob/main/rules/client-sdks/descope-react.mdc
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find the installed package version if present in lockfiles.
rg -n '"`@descope/react-sdk`"|`@descope/react-sdk`' package.json package-lock.json pnpm-lock.yaml yarn.lock npm-shrinkwrap.json -A2 -B2 || true
# Look for any vendored type definitions or docs in the repo that mention useUser fields.
rg -n 'isUserLoading|userLoading|useUser.*loading|useUser' . -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' -A3 -B3 || true
# If the package is installed in the sandbox cache, inspect exported declarations without executing repo code.
python3 - <<'PY'
import os, glob
candidates = []
for base in [os.getcwd(), '/tmp', '/workspace', '/root/.npm', '/root/.cache']:
for p in glob.glob(base + '/**/@descope/react-sdk*', recursive=True):
if os.path.isdir(p):
candidates.append(p)
print('\n'.join(candidates[:50]))
PYRepository: TanStack/cli
Length of output: 5146
🌐 Web query:
site:descope.com/docs/react-sdk useUser isUserLoading useSession isAuthenticated
💡 Result:
In Descope React SDK:
useSession()returnsisAuthenticatedandisSessionLoading. (docs.descope.com)useUser()returnsuserandisUserLoading.isUserLoadingmeans the user object is still loading. (docs.descope.com)- Both hooks must be used inside
AuthProvider. (docs.descope.com)
Typical usage:
const { isAuthenticated, isSessionLoading } = useSession()
const { user, isUserLoading } = useUser()Also, Descope notes that useSession() triggers a session refresh request automatically. (docs.descope.com)
Citations:
- 1: https://docs.descope.com/getting-started/react
- 2: https://docs.descope.com/getting-started/react
- 3: https://docs.descope.com/client-sdk/auth-helpers
- 4: https://docs.descope.com/client-sdk/auth-helpers
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,200p' packages/create/src/frameworks/react/add-ons/descope/assets/src/integrations/descope/header-user.tsxRepository: TanStack/cli
Length of output: 1264
Gate the avatar on isUserLoading as well. useUser() exposes a loading flag, so this component can render the 'U' fallback before the profile arrives; wait for user loading to finish before showing the initial.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/create/src/frameworks/react/add-ons/descope/assets/src/integrations/descope/header-user.tsx`
around lines 8 - 11, The avatar initial in header-user.tsx is rendered before
the user profile has finished loading, which can show the fallback too early.
Update the HeaderUser component to also guard on isUserLoading alongside
isSessionLoading and isAuthenticated, and only compute/render the initial after
user loading completes so the `useUser()` data is available before using
`user?.name` or `user?.email`.
| <button | ||
| type="button" | ||
| onClick={() => sdk.logout()} | ||
| className="rounded-xl px-3 py-1.5 text-sm font-semibold text-[var(--sea-ink-soft)] transition hover:bg-[var(--link-bg-hover)] hover:text-[var(--sea-ink)]" | ||
| > | ||
| Sign out | ||
| </button> |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== File outline ==\n'
ast-grep outline packages/create/src/frameworks/react/add-ons/descope/assets/src/integrations/descope/header-user.tsx --view expanded || true
printf '\n== Relevant file contents ==\n'
cat -n packages/create/src/frameworks/react/add-ons/descope/assets/src/integrations/descope/header-user.tsx
printf '\n== Search for logout definitions/usages ==\n'
rg -n "logout\\(" packages/create/src/frameworks/react/add-ons/descope -g '!**/node_modules/**' || trueRepository: TanStack/cli
Length of output: 1873
🌐 Web query:
Descope React SDK logout() returns Promise logout documentation
💡 Result:
The logout method provided by the Descope React SDK (accessed via the useDescope hook) returns a Promise, allowing you to use async/await syntax to handle post-logout logic such as navigation [1]. While some documentation examples show the method called without awaiting it [2], it is standard practice and recommended to await the logout call to ensure the session has been successfully invalidated before proceeding with client-side actions, such as redirecting the user to a login page [1]. Example usage: const { logout } = useDescope; const navigate = useNavigate; const handleLogout = async => { await logout; navigate('/login'); };
Citations:
- 1: https://github.com/descope/ai/blob/main/rules/client-sdks/descope-react.mdc
- 2: https://github.com/descope/descope-js/tree/main/packages/sdks/react-sdk
Handle the sdk.logout() promise
sdk.logout() returns a Promise, so the current click handler can drop a rejection on logout failure. Add a catch/await path and surface a failure state or fallback.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/create/src/frameworks/react/add-ons/descope/assets/src/integrations/descope/header-user.tsx`
around lines 24 - 30, The Sign out click handler in header-user.tsx ignores the
Promise returned by sdk.logout(), so logout failures can become unhandled
rejections. Update the onClick path around sdk.logout() to await it or attach a
catch, and surface a visible fallback or error state from this component so
failures are handled cleanly.
| ### What's wired up | ||
|
|
||
| - **`<AuthProvider>`** at the app root (`src/integrations/descope/provider.tsx`) supplies auth context to the whole tree | ||
| - **Header user menu** (`src/integrations/descope/header-user.tsx`) swaps between a "Sign in" link and the signed-in avatar + "Sign out" |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Header user menu does not render a "Sign in" link.
The README states the header user menu "swaps between a 'Sign in' link and the signed-in avatar + 'Sign out'". However, header-user.tsx returns null when !isAuthenticated — there is no "Sign in" link. Update the documentation to match the actual behavior.
-- **Header user menu** (`src/integrations/descope/header-user.tsx`) swaps between a "Sign in" link and the signed-in avatar + "Sign out"
+- **Header user menu** (`src/integrations/descope/header-user.tsx`) renders the signed-in avatar + "Sign out" button (hidden when signed out)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - **Header user menu** (`src/integrations/descope/header-user.tsx`) swaps between a "Sign in" link and the signed-in avatar + "Sign out" | |
| - **Header user menu** (`src/integrations/descope/header-user.tsx`) renders the signed-in avatar + "Sign out" button (hidden when signed out) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/create/src/frameworks/react/add-ons/descope/README.md` at line 16,
The README description for the Descope header user menu is inaccurate because
`header-user.tsx` does not render a “Sign in” link when unauthenticated. Update
the documentation text for the `header-user` integration so it matches the
actual `HeaderUser` behavior: it returns nothing when `!isAuthenticated` and
only shows the avatar and “Sign out” state when signed in.
Adds Descope as a managed authentication add-on for the React framework, alongside the existing Clerk and WorkOS options. Selecting it wires Descope auth into a generated TanStack Start app end-to-end.
What's included
<AuthProvider>(@descope/react-sdk) wrapped at the app root viaVITE_DESCOPE_PROJECT_IDuseSession/useUser/useDescope); renders nothing when signed out/demo/descoperendering thesign-up-or-inflow plus a signed-in greetingcategory: "auth"andexclusive: ["auth"]README.md(setup, route protection, production checklist) and.env.localentry@tanstack/create, minor)Summary by CodeRabbit