Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/descope-auth-addon.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tanstack/create": minor
---

Add Descope authentication add-on for React. Wires `<AuthProvider>` 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.
45 changes: 45 additions & 0 deletions packages/create/src/frameworks/react/add-ons/descope/README.md
Original file line number Diff line number Diff line change
@@ -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

- **`<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`) renders the signed-in avatar + "Sign out" button (hidden when signed out)
- **`/demo/descope`** renders the Descope `<Descope flowId="sign-up-or-in" />` 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 <p>Loading…</p>
if (!isAuthenticated) return <RedirectToSignIn />

return <YourPageContent />
}
```

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**
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Descope configuration, get your Project ID from the [Console](https://app.descope.com/settings/project)
VITE_DESCOPE_PROJECT_ID=
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useDescope, useSession, useUser } from '@descope/react-sdk'

export default function HeaderUser() {
const { isAuthenticated, isSessionLoading } = useSession()
const { user, isUserLoading } = useUser()
const sdk = useDescope()

if (isSessionLoading || isUserLoading || !isAuthenticated) return null

const email = user?.email
const initial = (user?.name || email || 'U').charAt(0).toUpperCase()

return (
<div className="flex items-center gap-2">
{user?.picture ? (
<img src={user.picture} alt="" className="h-8 w-8 rounded-full" />
) : (
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-neutral-200 dark:bg-neutral-800">
<span className="text-xs font-medium text-neutral-600 dark:text-neutral-400">
{initial}
</span>
</div>
)}
<button
type="button"
onClick={() =>
sdk.logout().catch((err) => console.error('Descope logout error', err))
}
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>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</div>
)
}
Original file line number Diff line number Diff line change
@@ -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 <AuthProvider projectId={PROJECT_ID}>{children}</AuthProvider>
}
Original file line number Diff line number Diff line change
@@ -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 (
<main className="demo-page demo-center">
<section className="demo-panel w-full max-w-md space-y-6">
{isSessionLoading ? (
<p className="demo-muted text-center text-sm">Loading…</p>
) : isAuthenticated ? (
<SignedInGreeting />
) : (
<SignedOut />
)}
</section>
</main>
)
}

function SignedOut() {
return (
<>
<div className="space-y-1.5">
<p className="island-kicker mb-2">Descope</p>
<h1 className="demo-title">Sign in to continue</h1>
<p className="demo-muted text-sm">
Descope renders the sign-in flow, manages sessions, and handles
passwordless, SSO, and social login for you.
</p>
</div>
<div className="flex justify-center pt-2">
<Descope
flowId="sign-up-or-in"
theme="light"
onError={(err) => console.error('Descope flow error', err)}
/>
</div>
<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>
</>
)
}

function SignedInGreeting() {
const { user } = useUser()
if (!user) return null

const email = user.email
const initial = (user.name || email || 'U').charAt(0).toUpperCase()

return (
<div className="space-y-6">
<div className="space-y-1.5">
<p className="island-kicker mb-2">Descope</p>
<h1 className="demo-title">Welcome back</h1>
<p className="demo-muted text-sm">You're signed in as {email}</p>
</div>

<div className="flex items-center gap-3">
{user.picture ? (
<img src={user.picture} alt="" className="h-10 w-10 rounded-full" />
) : (
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-neutral-200 dark:bg-neutral-800">
<span className="text-sm font-medium text-neutral-600 dark:text-neutral-400">
{initial}
</span>
</div>
)}
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium">{user.name}</p>
<p className="truncate text-xs text-neutral-500 dark:text-neutral-400">
{email}
</p>
</div>
</div>

<p className="demo-muted text-center text-xs">
Sign out from the avatar in the header. Built with{' '}
<a
href="https://descope.com"
target="_blank"
rel="noopener noreferrer"
className="font-medium"
>
DESCOPE
</a>
.
</p>
</div>
)
}
42 changes: 42 additions & 0 deletions packages/create/src/frameworks/react/add-ons/descope/info.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
34 changes: 34 additions & 0 deletions packages/create/src/frameworks/react/add-ons/descope/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dependencies": {
"@descope/react-sdk": "^2.30.3"
}
}
Loading