From 5b4ecb4038928b3d1273c7f6d82ace15a9ccf56a Mon Sep 17 00:00:00 2001 From: Maria Mantsurova Date: Wed, 8 Jul 2026 17:49:37 +0300 Subject: [PATCH] feat(create): add Descope auth add-on for React --- .changeset/descope-auth-addon.md | 5 + .../react/add-ons/descope/README.md | 45 ++++++++ .../descope/assets/_dot_env.local.append | 2 + .../src/integrations/descope/header-user.tsx | 33 ++++++ .../src/integrations/descope/provider.tsx | 14 +++ .../assets/src/routes/demo/descope.tsx | 107 ++++++++++++++++++ .../react/add-ons/descope/info.json | 42 +++++++ .../frameworks/react/add-ons/descope/logo.svg | 34 ++++++ .../react/add-ons/descope/package.json | 5 + .../react/add-ons/descope/small-logo.svg | 28 +++++ 10 files changed, 315 insertions(+) create mode 100644 .changeset/descope-auth-addon.md create mode 100644 packages/create/src/frameworks/react/add-ons/descope/README.md create mode 100644 packages/create/src/frameworks/react/add-ons/descope/assets/_dot_env.local.append create mode 100644 packages/create/src/frameworks/react/add-ons/descope/assets/src/integrations/descope/header-user.tsx create mode 100644 packages/create/src/frameworks/react/add-ons/descope/assets/src/integrations/descope/provider.tsx create mode 100644 packages/create/src/frameworks/react/add-ons/descope/assets/src/routes/demo/descope.tsx create mode 100644 packages/create/src/frameworks/react/add-ons/descope/info.json create mode 100644 packages/create/src/frameworks/react/add-ons/descope/logo.svg create mode 100644 packages/create/src/frameworks/react/add-ons/descope/package.json create mode 100644 packages/create/src/frameworks/react/add-ons/descope/small-logo.svg diff --git a/.changeset/descope-auth-addon.md b/.changeset/descope-auth-addon.md new file mode 100644 index 00000000..6efa5e12 --- /dev/null +++ b/.changeset/descope-auth-addon.md @@ -0,0 +1,5 @@ +--- +"@tanstack/create": minor +--- + +Add Descope authentication add-on for React. Wires `` at the app root via `VITE_DESCOPE_PROJECT_ID`, a header user menu, and a `/demo/descope` route rendering the `sign-up-or-in` flow. Mutually exclusive with other auth providers. diff --git a/packages/create/src/frameworks/react/add-ons/descope/README.md b/packages/create/src/frameworks/react/add-ons/descope/README.md new file mode 100644 index 00000000..26cb3cd8 --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/descope/README.md @@ -0,0 +1,45 @@ +## Setting up Descope + +1. Sign up at [descope.com](https://descope.com) and create a project +2. Copy your **Project ID** from the [Console](https://app.descope.com/settings/project) +3. Set it in your `.env.local`: + ```bash + VITE_DESCOPE_PROJECT_ID=P... + ``` +4. Make sure a **`sign-up-or-in`** flow exists in the Console (it ships by default) +5. Add `http://localhost:3000` to your project's approved domains / redirect URLs +6. Visit the demo route at `/demo/descope` once `npm run dev` is running + +### What's wired up + +- **``** 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" +- **`/demo/descope`** renders the Descope `` flow and a signed-in greeting + +### Protecting a route + +Use the `useSession` hook to gate content: + +```tsx +import { useSession } from '@descope/react-sdk' + +function ProtectedPage() { + const { isAuthenticated, isSessionLoading } = useSession() + + if (isSessionLoading) return

Loading…

+ if (!isAuthenticated) return + + return +} +``` + +For server-side checks (route loaders, server functions), validate the session +token with the [`@descope/node-sdk`](https://docs.descope.com/sdks/nodejs) and +`validateSession()`. + +### Production checklist + +- Use a dedicated **production project** and its Project ID +- Configure your production domain under **Project Settings → Domains** +- Customize your flows (branding, connectors, MFA) in the **Flows** section of the Console +- Enable the social / SSO connectors you need under **Authentication Methods** diff --git a/packages/create/src/frameworks/react/add-ons/descope/assets/_dot_env.local.append b/packages/create/src/frameworks/react/add-ons/descope/assets/_dot_env.local.append new file mode 100644 index 00000000..4a89c2a8 --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/descope/assets/_dot_env.local.append @@ -0,0 +1,2 @@ +# Descope configuration, get your Project ID from the [Console](https://app.descope.com/settings/project) +VITE_DESCOPE_PROJECT_ID= diff --git a/packages/create/src/frameworks/react/add-ons/descope/assets/src/integrations/descope/header-user.tsx b/packages/create/src/frameworks/react/add-ons/descope/assets/src/integrations/descope/header-user.tsx new file mode 100644 index 00000000..f5ff5a6c --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/descope/assets/src/integrations/descope/header-user.tsx @@ -0,0 +1,33 @@ +import { useDescope, useSession, useUser } from '@descope/react-sdk' + +export default function HeaderUser() { + const { isAuthenticated, isSessionLoading } = useSession() + const { user } = useUser() + const sdk = useDescope() + + if (isSessionLoading || !isAuthenticated) return null + + const email = user?.email + const initial = (user?.name || email || 'U').charAt(0).toUpperCase() + + return ( +
+ {user?.picture ? ( + + ) : ( +
+ + {initial} + +
+ )} + +
+ ) +} diff --git a/packages/create/src/frameworks/react/add-ons/descope/assets/src/integrations/descope/provider.tsx b/packages/create/src/frameworks/react/add-ons/descope/assets/src/integrations/descope/provider.tsx new file mode 100644 index 00000000..b4d31dcb --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/descope/assets/src/integrations/descope/provider.tsx @@ -0,0 +1,14 @@ +import { AuthProvider } from '@descope/react-sdk' + +const PROJECT_ID = import.meta.env.VITE_DESCOPE_PROJECT_ID +if (!PROJECT_ID) { + throw new Error('Add your Descope Project ID to the .env.local file') +} + +export default function AppDescopeProvider({ + children, +}: { + children: React.ReactNode +}) { + return {children} +} diff --git a/packages/create/src/frameworks/react/add-ons/descope/assets/src/routes/demo/descope.tsx b/packages/create/src/frameworks/react/add-ons/descope/assets/src/routes/demo/descope.tsx new file mode 100644 index 00000000..466546db --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/descope/assets/src/routes/demo/descope.tsx @@ -0,0 +1,107 @@ +import { createFileRoute } from '@tanstack/react-router' +import { Descope, useSession, useUser } from '@descope/react-sdk' + +export const Route = createFileRoute('/demo/descope')({ + component: DescopeDemo, +}) + +function DescopeDemo() { + const { isAuthenticated, isSessionLoading } = useSession() + + return ( +
+
+ {isSessionLoading ? ( +

Loading…

+ ) : isAuthenticated ? ( + + ) : ( + + )} +
+
+ ) +} + +function SignedOut() { + return ( + <> +
+

Descope

+

Sign in to continue

+

+ Descope renders the sign-in flow, manages sessions, and handles + passwordless, SSO, and social login for you. +

+
+
+ console.error('Descope flow error', err)} + /> +
+

+ Built with{' '} + + DESCOPE + + . +

+ + ) +} + +function SignedInGreeting() { + const { user } = useUser() + if (!user) return null + + const email = user.email + const initial = (user.name || email || 'U').charAt(0).toUpperCase() + + return ( +
+
+

Descope

+

Welcome back

+

You're signed in as {email}

+
+ +
+ {user.picture ? ( + + ) : ( +
+ + {initial} + +
+ )} +
+

{user.name}

+

+ {email} +

+
+
+ +

+ Sign out from the avatar in the header. Built with{' '} + + DESCOPE + + . +

+
+ ) +} diff --git a/packages/create/src/frameworks/react/add-ons/descope/info.json b/packages/create/src/frameworks/react/add-ons/descope/info.json new file mode 100644 index 00000000..66302e86 --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/descope/info.json @@ -0,0 +1,42 @@ +{ + "name": "Descope", + "description": "Drop-in auth with hosted flows, passwordless, SSO, and social login (managed).", + "phase": "add-on", + "modes": ["file-router"], + "type": "add-on", + "category": "auth", + "exclusive": ["auth"], + "color": "#0081B3", + "priority": 170, + "link": "https://descope.com", + "routes": [ + { + "icon": "CircleUserRound", + "url": "/demo/descope", + "name": "Descope", + "path": "src/routes/demo.descope.tsx", + "jsName": "DescopeDemo" + } + ], + "integrations": [ + { + "type": "header-user", + "jsName": "DescopeHeader", + "path": "src/integrations/descope/header-user.tsx" + }, + { + "type": "provider", + "jsName": "DescopeProvider", + "path": "src/integrations/descope/provider.tsx" + } + ], + "envVars": [ + { + "name": "VITE_DESCOPE_PROJECT_ID", + "description": "Descope project ID", + "required": true, + "secret": false, + "file": ".env.local" + } + ] +} diff --git a/packages/create/src/frameworks/react/add-ons/descope/logo.svg b/packages/create/src/frameworks/react/add-ons/descope/logo.svg new file mode 100644 index 00000000..1c1539e1 --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/descope/logo.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/create/src/frameworks/react/add-ons/descope/package.json b/packages/create/src/frameworks/react/add-ons/descope/package.json new file mode 100644 index 00000000..4ed93902 --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/descope/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@descope/react-sdk": "^2.30.3" + } +} diff --git a/packages/create/src/frameworks/react/add-ons/descope/small-logo.svg b/packages/create/src/frameworks/react/add-ons/descope/small-logo.svg new file mode 100644 index 00000000..96717294 --- /dev/null +++ b/packages/create/src/frameworks/react/add-ons/descope/small-logo.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +