├── .dockerignore ├── .env.example ├── .eslintrc.json ├── .gitignore ├── .idea ├── .gitignore ├── example-passkeys-nextjs.iml ├── inspectionProfiles │ └── Project_Default.xml ├── modules.xml ├── prettier.xml └── vcs.xml ├── Dockerfile ├── LICENSE ├── README.md ├── app ├── _utils │ ├── LogoutButton.tsx │ ├── PasskeyList.tsx │ ├── Provider.tsx │ └── nodeSdk.ts ├── favicon.ico ├── globals.css ├── layout.tsx ├── page.tsx └── profile │ └── page.tsx ├── docker-compose.yml ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── next.svg └── vercel.svg ├── tailwind.config.ts └── tsconfig.json /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | .dockerignore 3 | node_modules 4 | npm-debug.log 5 | README.md 6 | .next 7 | .git -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_CORBADO_PROJECT_ID= 2 | CORBADO_API_SECRET= 3 | CORBADO_FRONTEND_API= 4 | CORBADO_BACKEND_API= 5 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | .idea 4 | 5 | # dependencies 6 | /node_modules 7 | /.pnp 8 | .pnp.js 9 | 10 | # testing 11 | /coverage 12 | 13 | # next.js 14 | /.next/ 15 | /out/ 16 | 17 | # production 18 | /build 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # local env files 30 | .env*.local 31 | .env 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | next-env.d.ts 39 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/example-passkeys-nextjs.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/prettier.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine AS base 2 | 3 | # Install dependencies only when needed 4 | FROM base AS deps 5 | # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. 6 | RUN apk add --no-cache libc6-compat 7 | WORKDIR /app 8 | 9 | # Install dependencies based on the preferred package manager 10 | COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ 11 | RUN \ 12 | if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ 13 | elif [ -f package-lock.json ]; then npm ci; \ 14 | elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \ 15 | else echo "Lockfile not found." && exit 1; \ 16 | fi 17 | 18 | 19 | # Rebuild the source code only when needed 20 | FROM base AS builder 21 | WORKDIR /app 22 | COPY --from=deps /app/node_modules ./node_modules 23 | COPY . . 24 | 25 | # Next.js collects completely anonymous telemetry data about general usage. 26 | # Learn more here: https://nextjs.org/telemetry 27 | # Uncomment the following line in case you want to disable telemetry during the build. 28 | ENV NEXT_TELEMETRY_DISABLED 1 29 | 30 | RUN \ 31 | if [ -f yarn.lock ]; then yarn run build; \ 32 | elif [ -f package-lock.json ]; then npm run build; \ 33 | elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build; \ 34 | else echo "Lockfile not found." && exit 1; \ 35 | fi 36 | 37 | # Production image, copy all the files and run next 38 | FROM base AS runner 39 | WORKDIR /app 40 | 41 | ENV NODE_ENV production 42 | # Uncomment the following line in case you want to disable telemetry during runtime. 43 | # ENV NEXT_TELEMETRY_DISABLED 1 44 | 45 | RUN addgroup --system --gid 1001 nodejs 46 | RUN adduser --system --uid 1001 nextjs 47 | 48 | COPY --from=builder /app/public ./public 49 | 50 | # Set the correct permission for prerender cache 51 | RUN mkdir .next 52 | RUN chown nextjs:nodejs .next 53 | 54 | # Automatically leverage output traces to reduce image size 55 | # https://nextjs.org/docs/advanced-features/output-file-tracing 56 | COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ 57 | COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static 58 | 59 | USER nextjs 60 | 61 | EXPOSE 3000 62 | 63 | ENV PORT 3000 64 | # set hostname to localhost 65 | ENV HOSTNAME "0.0.0.0" 66 | 67 | # server.js is created by next build from the standalone output 68 | # https://nextjs.org/docs/pages/api-reference/next-config-js/output 69 | CMD ["node", "server.js"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Corbado GmbH 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GitHub Repo Cover 2 | 3 | # Next.js (TypeScript) Passkey Example App 4 | 5 | This is a sample implementation of the Corbado React package and Corbado Node SDK being integrated into a web application built with Next.js. 6 | 7 | Please see the [full blog post](https://www.corbado.com/blog/nextjs-passkeys) to understand the detailed steps needed to integrate passkeys into Next.js apps. 8 | 9 | ## File structure 10 | 11 | - `app/auth/page.tsx`: the file where the React Auth component is used 12 | - `app/profile/page.tsx`: displays user information if the user has successfully authenticated (rendered on the server) 13 | - `app/user-data/route.ts`: api route that checks the users authentication state using the Corbado Node SDK 14 | - `app/page.tsx`: client rendered page that accesses data from the above mentioned route 15 | - `.env.local`: add relevant environment variables that you can obtain 16 | from [Corbado developer panel](https://app.corbado.com/signin#register) 17 | 18 | ## Setup 19 | 20 | ### Prerequisites 21 | 22 | Please follow the steps in [Getting started](https://docs.corbado.com/overview/getting-started) to create and configure 23 | a project in the [Corbado developer panel](https://app.corbado.com/signin#register). 24 | 25 | You need to have [Node](https://nodejs.org/en/download) and `npm` installed to run it. 26 | 27 | ### Configure environment variables 28 | 29 | Use the values you obtained in [Prerequisites](#prerequisites) to configure the following variables inside an `.env.local` 30 | file you create in the root folder of this project: 31 | 32 | ```sh 33 | NEXT_PUBLIC_CORBADO_PROJECT_ID= 34 | CORBADO_API_SECRET= 35 | CORBADO_FRONTEND_API= 36 | CORBADO_BACKEND_API= 37 | ``` 38 | 39 | ## Usage 40 | 41 | ### Run the project locally 42 | 43 | Run 44 | 45 | ```bash 46 | npm i 47 | ``` 48 | 49 | to install all dependencies. 50 | 51 | Finally, you can run the project locally with 52 | 53 | ```bash 54 | npm run dev 55 | ``` 56 | 57 | ### Run the project in a docker container 58 | 59 | You can also run the project in a docker container using the provided `docker-compose.yml` file. 60 | 61 | ```bash 62 | docker-compose up --build 63 | ``` 64 | -------------------------------------------------------------------------------- /app/_utils/LogoutButton.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useCorbado } from "@corbado/react" 4 | import { useRouter } from "next/navigation" 5 | 6 | export default function LogoutButton() { 7 | const { logout } = useCorbado() 8 | const router = useRouter() 9 | 10 | const onLogout = () => { 11 | logout() 12 | router.push("/") 13 | } 14 | 15 | return ( 16 | 17 | ) 18 | } -------------------------------------------------------------------------------- /app/_utils/PasskeyList.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import {PasskeyList as CorbadoPasskeyList} from "@corbado/react"; 4 | 5 | export default function PasskeyList() { 6 | return ( 7 | 8 | ) 9 | } -------------------------------------------------------------------------------- /app/_utils/Provider.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { CorbadoProvider } from "@corbado/react" 4 | 5 | export default function Provider({ 6 | children, 7 | }: { 8 | children: React.ReactNode 9 | }) { 10 | return ( 11 | 15 | {children} 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /app/_utils/nodeSdk.ts: -------------------------------------------------------------------------------- 1 | import { Config, SDK } from '@corbado/node-sdk'; 2 | 3 | const projectID = process.env.NEXT_PUBLIC_CORBADO_PROJECT_ID!; 4 | const apiSecret = process.env.CORBADO_API_SECRET! 5 | const frontendAPI = process.env.CORBADO_FRONTEND_API!; 6 | const backendAPI = process.env.CORBADO_BACKEND_API!; 7 | 8 | const config = new Config(projectID, apiSecret, frontendAPI, backendAPI); 9 | const sdk = new SDK(config); 10 | 11 | export default function getNodeSDK() { 12 | return sdk; 13 | } -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/corbado/example-passkeys-nextjs/a5bc50046871523b6e1f86802f67f69309f8d09b/app/favicon.ico -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html { 6 | font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", 7 | Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, 8 | "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", 9 | "Noto Color Emoji"; 10 | line-height: 1.5; 11 | -webkit-text-size-adjust: 100%; 12 | -webkit-font-smoothing: antialiased; 13 | } 14 | 15 | body { 16 | text-align: center; 17 | margin-top: 25px; 18 | color: #333; 19 | font-family: inherit; 20 | line-height: inherit; 21 | max-width: 600px; 22 | margin-left: auto; 23 | margin-right: auto; 24 | } 25 | a { 26 | color: blue; 27 | text-decoration: underline; 28 | } 29 | h1 { 30 | font-size: 2.5rem; 31 | } 32 | h2 { 33 | font-size: 2.2rem; 34 | } 35 | h3 { 36 | font-size: 2rem; 37 | } 38 | h4 { 39 | font-size: 1.8rem; 40 | } 41 | h5 { 42 | font-size: 1.6rem; 43 | } 44 | h6 { 45 | font-size: 1.4rem; 46 | } 47 | p, 48 | a, 49 | button { 50 | font-size: 1.2rem; 51 | } 52 | button { 53 | background-color: rgb(10, 10, 200); 54 | color: white; 55 | padding: 5px 20px; 56 | border-radius: 5px; 57 | margin: 0px 10px; 58 | border: 0; 59 | } 60 | button:hover { 61 | background-color: rgba(10, 10, 200, 0.8); 62 | } 63 | .Auth { 64 | display: flex; 65 | justify-content: center; 66 | } 67 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "./globals.css" 2 | import type {Metadata} from "next" 3 | import {Inter} from "next/font/google" 4 | import CorbadoProvider from "@/app/_utils/Provider" 5 | 6 | const inter = Inter({subsets: ["latin"]}) 7 | 8 | export const metadata: Metadata = { 9 | title: "Next Corbado Example", 10 | description: "Go passwordless with Corbado and Next.js", 11 | } 12 | 13 | export default function RootLayout( 14 | { 15 | children, 16 | }: { 17 | children: React.ReactNode 18 | }) { 19 | return ( 20 | 21 | 22 | {children} 23 | 24 | 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { CorbadoAuth } from "@corbado/react" 4 | import { useRouter } from "next/navigation" 5 | 6 | export default function Auth() { 7 | 8 | const router = useRouter() 9 | const onLoggedIn = () => { 10 | router.push("/profile") 11 | } 12 | 13 | return ( 14 |
15 | 16 |
17 | ) 18 | } -------------------------------------------------------------------------------- /app/profile/page.tsx: -------------------------------------------------------------------------------- 1 | import {cookies} from "next/headers"; 2 | import getNodeSDK from "@/app/_utils/nodeSdk"; 3 | import {redirect} from "next/navigation"; 4 | import LogoutButton from "@/app/_utils/LogoutButton"; 5 | import PasskeyList from "@/app/_utils/PasskeyList"; 6 | 7 | // the user data will be retrieved server side 8 | export default async function Profile() { 9 | const cookieStore = cookies(); 10 | const sessionToken = cookieStore.get("cbo_session_token"); 11 | if (!sessionToken) { 12 | return redirect("/"); 13 | } 14 | 15 | const sdk = getNodeSDK(); 16 | let user; 17 | try { 18 | user = await sdk.sessions().validateToken(sessionToken.value); 19 | } catch { 20 | return redirect("/"); 21 | } 22 | 23 | return ( 24 |
25 |

Profile Page

26 |

27 | User-ID: {user.userId}
28 | Full name: {user.fullName} 29 |

30 | 31 | 32 |
33 | ) 34 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | nextjs: 5 | build: 6 | context: . 7 | ports: 8 | - '5113:3000' 9 | environment: 10 | - NODE_ENV=production 11 | user: 'nextjs' # Ensure this user is created in your Dockerfile with the appropriate permissions 12 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | output: "standalone", 4 | env: { 5 | CORBADO_API_SECRET: process.env.CORBADO_API_SECRET, 6 | }, 7 | publicRuntimeConfig: { 8 | // Will be available on both server and client 9 | nextPublicCorbadoProjectId: process.env.NEXT_PUBLIC_CORBADO_PROJECT_ID, 10 | }, 11 | serverRuntimeConfig: { 12 | // Will only be available on the server side 13 | corbadoApiSecret: process.env.CORBADO_API_SECRET, 14 | }, 15 | } 16 | 17 | module.exports = nextConfig; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next.js", 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 | "@corbado/node-sdk": "^3.0.2", 13 | "@corbado/react": "^2.17.0", 14 | "@types/node": "20.11.24", 15 | "@types/react": "18.2.61", 16 | "@types/react-dom": "18.2.19", 17 | "autoprefixer": "10.4.17", 18 | "eslint": "8.57.0", 19 | "eslint-config-next": "14.1.0", 20 | "next": "14.1.0", 21 | "postcss": "8.4.35", 22 | "react": "18.2.0", 23 | "react-dom": "18.2.0", 24 | "tailwindcss": "3.4.1", 25 | "typescript": "5.3.3" 26 | }, 27 | "devDependencies": { 28 | "@corbado/types": "^2.5.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------