├── .eslintrc.json ├── app ├── favicon.ico ├── api │ └── audit │ │ └── route.tsx ├── page.tsx ├── Descope.tsx ├── dashboard │ ├── logout-button.tsx │ └── page.tsx ├── globals.css ├── signup │ └── page.tsx ├── login │ └── page.tsx ├── AuthProvider.tsx └── layout.tsx ├── next.config.js ├── postcss.config.js ├── auth.config.ts ├── lib ├── descopeClient.ts └── helpers.ts ├── .gitignore ├── tailwind.config.ts ├── public ├── vercel.svg └── next.svg ├── tsconfig.json ├── components └── layout │ └── nav.tsx ├── package.json ├── LICENSE ├── middleware.ts ├── README.md └── descope_middleware ├── helpers.ts ├── archive.ts └── index.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/descope-sample-apps/next-app-router-sample-app/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /auth.config.ts: -------------------------------------------------------------------------------- 1 | import { DescopeMiddlewareConfig } from "./descope_middleware"; 2 | 3 | 4 | export const authConfig = { 5 | 6 | } satisfies DescopeMiddlewareConfig; 7 | -------------------------------------------------------------------------------- /app/api/audit/route.tsx: -------------------------------------------------------------------------------- 1 | export async function POST(request: Request) { 2 | const body = await request.json(); 3 | console.log(body) 4 | return new Response(JSON.stringify(body), { 5 | headers: { 'content-type': 'application/json' }, 6 | }); 7 | } -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | 3 | export default function Home() { 4 | return ( 5 |
6 |

7 | Welcome to Descope's NextJS Sample App! 8 |

9 |
10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /app/Descope.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | // TODO: Check why we can't just import Descope 4 | // from '@descope/react-sdk' and directly export it 5 | 6 | import dynamic from 'next/dynamic'; 7 | const Descope = dynamic( 8 | () => import('@descope/react-sdk').then(module => module.Descope), 9 | { ssr: false } 10 | ); 11 | 12 | export default Descope; -------------------------------------------------------------------------------- /lib/descopeClient.ts: -------------------------------------------------------------------------------- 1 | import DescopeClient from '@descope/node-sdk'; 2 | 3 | if (!process.env.NEXT_PUBLIC_DESCOPE_PROJECT_ID) { 4 | throw new Error('NEXT_PUBLIC_DESCOPE_PROJECT_ID is not defined'); 5 | } 6 | 7 | 8 | 9 | const descope = DescopeClient({ projectId: process.env.NEXT_PUBLIC_DESCOPE_PROJECT_ID, managementKey: process.env.DESCOPE_MANAGEMENT_KEY }); 10 | export default descope; -------------------------------------------------------------------------------- /app/dashboard/logout-button.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useDescope } from "@descope/react-sdk"; 4 | 5 | export default function LogoutButton() { 6 | const { logout } = useDescope(); 7 | const onLogout = async () => { 8 | await logout(); 9 | window.location.href = '/'; 10 | } 11 | return ( 12 | 13 | ) 14 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss' 2 | 3 | const config: Config = { 4 | content: [ 5 | './pages/**/*.{js,ts,jsx,tsx,mdx}', 6 | './components/**/*.{js,ts,jsx,tsx,mdx}', 7 | './app/**/*.{js,ts,jsx,tsx,mdx}', 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 13 | 'gradient-conic': 14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | } 20 | export default config 21 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } */ 28 | -------------------------------------------------------------------------------- /lib/helpers.ts: -------------------------------------------------------------------------------- 1 | 'use server' 2 | 3 | import { getSessionToken } from "@/descope_middleware/helpers"; 4 | import descope from "./descopeClient"; 5 | 6 | const getServerSession = async () => { 7 | const sessionToken = getSessionToken(); 8 | const session = await descope.validateSession(sessionToken!); 9 | return session; 10 | } 11 | 12 | const getServerSessionUser = async () => { 13 | const session = await getServerSession(); 14 | const userId = session.token.sub; 15 | const userRes = await descope.management.user.loadByUserId(userId!); 16 | const user = userRes.data; 17 | return user; 18 | } 19 | 20 | export { getServerSession, getServerSessionUser }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /app/signup/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useEffect } from 'react'; 4 | import Descope from '../Descope'; 5 | import { useSession, useUser } from '@descope/react-sdk'; 6 | 7 | export default function Page() { 8 | 9 | const { isAuthenticated, isSessionLoading } = useSession(); 10 | const { isUserLoading } = useUser(); 11 | // Redirect to login if not authenticated 12 | useEffect(() => { 13 | if (!(isSessionLoading || isUserLoading) && isAuthenticated) { 14 | window.location.href = '/dashboard'; 15 | } 16 | }); 17 | 18 | return ( 19 |
20 | 23 |
24 | ) 25 | } -------------------------------------------------------------------------------- /app/login/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useEffect } from 'react'; 4 | import Descope from '../Descope'; 5 | import { useDescope, useSession, useUser } from "@descope/react-sdk" 6 | 7 | export default function Page() { 8 | 9 | const { isAuthenticated, isSessionLoading } = useSession(); 10 | const { isUserLoading } = useUser(); 11 | 12 | // Redirect to login if not authenticated 13 | useEffect(() => { 14 | if (!(isSessionLoading || isUserLoading) && isAuthenticated) { 15 | window.location.href = '/dashboard'; 16 | } 17 | }); 18 | 19 | return ( 20 |
21 | 24 |
25 | ) 26 | } -------------------------------------------------------------------------------- /components/layout/nav.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | export default function Nav() { 4 | return
7 |
8 | 9 |

Descope

10 | 11 |
12 | Log in 13 | 17 | Sign up 18 | 19 |
20 |
21 |
22 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@descope/node-sdk": "^1.6.1", 13 | "@descope/react-sdk": "^2.0.3", 14 | "@descope/web-js-sdk": "^1.7.2", 15 | "jose": "^5.1.3", 16 | "next": "14.0.3", 17 | "react": "^18", 18 | "react-dom": "^18" 19 | }, 20 | "devDependencies": { 21 | "@types/node": "^20", 22 | "@types/react": "^18", 23 | "@types/react-dom": "^18", 24 | "autoprefixer": "^10.0.1", 25 | "eslint": "^8", 26 | "eslint-config-next": "14.0.3", 27 | "postcss": "^8", 28 | "tailwindcss": "^3.3.0", 29 | "typescript": "^5" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/AuthProvider.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { AuthProvider as ReactAuthProvider } from '@descope/react-sdk'; 4 | import { ReactElement } from 'react'; 5 | 6 | interface AuthProviderProps { 7 | children: ReactElement; 8 | projectId: string; 9 | baseUrl?: string; 10 | } 11 | 12 | const AuthProvider = ({ children, projectId, baseUrl }: AuthProviderProps ) => { 13 | return ( 14 | 22 | {children} 23 | 24 | ); 25 | } 26 | 27 | export default AuthProvider; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Descope Sample Apps 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import { Inter } from 'next/font/google' 3 | import './globals.css' 4 | import AuthProvider from './AuthProvider'; 5 | import Nav from '@/components/layout/nav'; 6 | 7 | const inter = Inter({ subsets: ['latin'] }) 8 | 9 | export const metadata: Metadata = { 10 | title: 'Create Next App', 11 | description: 'Generated by create next app', 12 | } 13 | 14 | export default function RootLayout({ 15 | children, 16 | }: { 17 | children: React.ReactNode 18 | }) { 19 | 20 | if (!process.env.NEXT_PUBLIC_DESCOPE_PROJECT_ID) { 21 | throw new Error('NEXT_PUBLIC_DESCOPE_PROJECT_ID is not defined'); 22 | } 23 | 24 | return ( 25 | 29 | 30 | 31 |