-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
feat: add Descope #7756
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?
feat: add Descope #7756
Changes from 2 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,3 @@ | ||
| { | ||
| "image": "mcr.microsoft.com/devcontainers/typescript-node:24" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| # Your Descope Project ID (found at https://app.descope.com/settings/project). | ||
| # Exposed to the browser for the client-side auth flow, so it must be VITE_ prefixed. | ||
| VITE_DESCOPE_PROJECT_ID=<YOUR_PROJECT_ID> | ||
|
|
||
| # The same Project ID, read by the server to validate sessions. | ||
| DESCOPE_PROJECT_ID=<YOUR_PROJECT_ID> | ||
|
|
||
| # Optional. Only needed for a custom domain (CNAME), a self-hosted Descope | ||
| # instance, or a non-default region (e.g. the EU cluster: https://api.euc1.descope.com). | ||
| # VITE_DESCOPE_BASE_URL= | ||
| # DESCOPE_BASE_URL= |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| node_modules | ||
| # Keep environment variables out of version control | ||
| !.env.example | ||
| package-lock.json | ||
| yarn.lock | ||
|
|
||
| .DS_Store | ||
| .cache | ||
| .env | ||
| .vercel | ||
| .output | ||
| /build/ | ||
| /api/ | ||
| /server/build | ||
| /public/build# Sentry Config File | ||
| .env.sentry-build-plugin | ||
| /test-results/ | ||
| /playwright-report/ | ||
| /blob-report/ | ||
| /playwright/.cache/ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| **/build | ||
| **/public | ||
| pnpm-lock.yaml | ||
| routeTree.gen.ts |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "files.watcherExclude": { | ||
| "**/routeTree.gen.ts": true | ||
| }, | ||
| "search.exclude": { | ||
| "**/routeTree.gen.ts": true | ||
| }, | ||
| "files.readonlyInclude": { | ||
| "**/routeTree.gen.ts": true | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| # TanStack Start + Descope | ||
|
|
||
| A [TanStack Start](https://tanstack.com/start) example demonstrating how to authenticate users with [Descope](https://www.descope.com): the [`@descope/react-sdk`](https://www.npmjs.com/package/@descope/react-sdk) renders the sign-in flow on the client, and the [`@descope/node-sdk`](https://www.npmjs.com/package/@descope/node-sdk) validates sessions on the server during SSR. | ||
|
|
||
| - [TanStack Router Docs](https://tanstack.com/router) | ||
| - [Descope Documentation](https://docs.descope.com) | ||
|
|
||
| ## Start a new project based on this example | ||
|
|
||
| To start a new project based on this example, run: | ||
|
|
||
| ```sh | ||
| npx gitpick TanStack/router/tree/main/examples/react/start-descope start-descope | ||
| ``` | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| You will need a [Descope account](https://www.descope.com/sign-up). Every project ships with a default `sign-up-or-in` flow, which is what this example renders — no extra flow setup is required. | ||
|
|
||
| ## Running the example | ||
|
|
||
| 1. Grab your **Project ID** from the [Descope console](https://app.descope.com/settings/project). | ||
| 2. Copy `.env.example` to `.env` and fill in your Project ID: | ||
|
|
||
| ```bash | ||
| cp .env.example .env | ||
| ``` | ||
|
|
||
| ```bash | ||
| # Exposed to the browser for the client-side auth flow (must be VITE_ prefixed) | ||
| VITE_DESCOPE_PROJECT_ID=<YOUR_PROJECT_ID> | ||
| # The same Project ID, read by the server to validate sessions | ||
| DESCOPE_PROJECT_ID=<YOUR_PROJECT_ID> | ||
| ``` | ||
|
|
||
| > [!NOTE] | ||
| > If you are using a custom domain (CNAME), or a private environment (for example `star`), also set `VITE_DESCOPE_BASE_URL` and `DESCOPE_BASE_URL`. | ||
|
|
||
| 3. Run the following command and navigate to [http://localhost:3000](http://localhost:3000). | ||
|
|
||
| ```bash | ||
| pnpm dev | ||
| ``` | ||
|
|
||
| ## Build | ||
|
|
||
| Build for production: | ||
|
|
||
| ```sh | ||
| pnpm build | ||
| ``` | ||
|
|
||
| ## About This Example | ||
|
|
||
| This example demonstrates: | ||
|
|
||
| - Descope authentication via the hosted `sign-up-or-in` flow (`<Descope flowId="sign-up-or-in" />`) | ||
| - Self-service account management via the `<UserProfile />` widget (name, avatar, passkeys, MFA) | ||
| - Server-side session validation during SSR with `@descope/node-sdk`, wired through TanStack Start request middleware | ||
| - Protected routes guarded in `beforeLoad` | ||
| - User session management (login, logout, session refresh) | ||
|
|
||
| ### How it works | ||
|
|
||
| The Descope integration lives in `src/integrations/descope/`: | ||
|
|
||
| - **`provider.tsx`** — a client `<DescopeProvider>` wrapping `<AuthProvider>` from `@descope/react-sdk`. Passing `sessionTokenViaCookie` stores the session token in a cookie so it is sent with the SSR document request (rather than living only in local storage). Used in `src/routes/__root.tsx`. | ||
| - **`middleware.ts`** — `descopeMiddleware()`, registered in `src/start.ts` as a TanStack Start `requestMiddleware`. It runs on every server request, validates/refreshes the session, and puts the user on the global start context. | ||
| - **`session.server.ts`** — the server-only logic: reads the `DS` (session) and `DSR` (refresh) cookies, calls `validateAndRefreshSession`, and persists a rotated session cookie. This is the only module that imports `@descope/node-sdk`. | ||
| - **`server.ts`** — a `getSession` server function that reads the user off the global context (works during SSR and client-side navigation). The root route's `beforeLoad` exposes it as `context.user`. | ||
|
|
||
| Routes: | ||
|
|
||
| - **Login** — `src/routes/login.tsx` renders the `<Descope>` flow component. On success it invalidates the router so the server re-reads the new session. | ||
| - **Protected routes** — `src/routes/_authed.tsx` redirects to `/login` when there is no `context.user`; `src/routes/_authed/profile.tsx` renders the Descope `<UserProfile />` widget (every project ships a default `user-profile-widget`) and shows the resolved session. | ||
| - **Logout** — `src/routes/logout.tsx` calls `useDescope().logout()` and clears the server cookies. |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,33 @@ | ||||||||||
| { | ||||||||||
| "name": "tanstack-start-example-descope", | ||||||||||
| "private": true, | ||||||||||
| "main": "index.js", | ||||||||||
| "type": "module", | ||||||||||
| "scripts": { | ||||||||||
| "dev": "vite dev", | ||||||||||
| "build": "vite build && tsc --noEmit", | ||||||||||
| "preview": "vite preview", | ||||||||||
| "start": "pnpx srvx --prod -s ../client dist/server/server.js" | ||||||||||
| }, | ||||||||||
| "keywords": [], | ||||||||||
| "author": "", | ||||||||||
| "license": "MIT", | ||||||||||
| "dependencies": { | ||||||||||
| "@descope/node-sdk": "^2.12.0", | ||||||||||
| "@descope/react-sdk": "^2.30.3", | ||||||||||
| "@tanstack/react-router": "^1.170.17", | ||||||||||
| "@tanstack/react-router-devtools": "^1.167.0", | ||||||||||
|
Comment on lines
+18
to
+19
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. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Version mismatch between
🔧 Proposed fix- "`@tanstack/react-router-devtools`": "^1.167.0",
+ "`@tanstack/react-router-devtools`": "^1.170.0",📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| "@tanstack/react-start": "^1.168.27", | ||||||||||
| "react": "^19.0.0", | ||||||||||
| "react-dom": "^19.0.0" | ||||||||||
| }, | ||||||||||
| "devDependencies": { | ||||||||||
| "@tailwindcss/vite": "^4.2.2", | ||||||||||
| "@types/react": "^19.0.8", | ||||||||||
| "@types/react-dom": "^19.0.3", | ||||||||||
| "@vitejs/plugin-react": "^6.0.1", | ||||||||||
| "tailwindcss": "^4.2.2", | ||||||||||
| "typescript": "^6.0.2", | ||||||||||
| "vite": "^8.0.14" | ||||||||||
| } | ||||||||||
| } | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import { | ||
| ErrorComponent, | ||
| Link, | ||
| useLocation, | ||
| useRouter, | ||
| } from '@tanstack/react-router' | ||
| import type { ErrorComponentProps } from '@tanstack/react-router' | ||
|
|
||
| export function DefaultCatchBoundary({ error }: ErrorComponentProps) { | ||
| const router = useRouter() | ||
| const isRoot = useLocation({ | ||
| select: (location) => location.pathname === '/', | ||
| }) | ||
|
|
||
| console.error(error) | ||
|
|
||
| return ( | ||
| <div className="min-w-0 flex-1 p-4 flex flex-col items-center justify-center gap-6"> | ||
| <ErrorComponent error={error} /> | ||
| <div className="flex gap-2 items-center flex-wrap"> | ||
| <button | ||
| onClick={() => { | ||
| router.invalidate() | ||
| }} | ||
| className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`} | ||
| > | ||
| Try Again | ||
| </button> | ||
| {isRoot ? ( | ||
| <Link | ||
| to="/" | ||
| className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`} | ||
| > | ||
| Home | ||
| </Link> | ||
| ) : ( | ||
| <Link | ||
| to="/" | ||
| className={`px-2 py-1 bg-gray-600 dark:bg-gray-700 rounded-sm text-white uppercase font-extrabold`} | ||
| onClick={(e) => { | ||
| e.preventDefault() | ||
| window.history.back() | ||
| }} | ||
| > | ||
| Go Back | ||
| </Link> | ||
| )} | ||
| </div> | ||
| </div> | ||
| ) | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.