├── .env.local.example ├── .github └── FUNDING.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── app ├── [...proxy] │ └── route.tsx ├── api-example │ └── page.tsx ├── api │ └── protected │ │ └── route.ts ├── auth │ └── [...nextauth] │ │ └── route.ts ├── client-example │ └── page.tsx ├── favicon.ico ├── globals.css ├── layout.tsx ├── middleware-example │ └── page.tsx ├── page.tsx ├── policy │ └── page.tsx └── server-example │ └── page.tsx ├── auth.ts ├── components.json ├── components ├── access-denied.tsx ├── auth-components.tsx ├── client-example.tsx ├── custom-link.tsx ├── footer.module.css ├── footer.tsx ├── header.module.css ├── header.tsx ├── layout.tsx ├── main-nav.tsx ├── session-data.tsx ├── ui │ ├── avatar.tsx │ ├── button.tsx │ ├── dropdown-menu.tsx │ ├── input.tsx │ └── navigation-menu.tsx └── user-button.tsx ├── docker-compose.yml ├── lib └── utils.ts ├── middleware.ts ├── next-env.d.ts ├── next.config.js ├── package.json ├── postcss.config.js ├── public └── logo.png ├── tailwind.config.js ├── test-docker.sh └── tsconfig.json /.env.local.example: -------------------------------------------------------------------------------- 1 | AUTH_SECRET= # `npx auth secret` or `openssl rand -hex 32` 2 | 3 | AUTH_AUTH0_ID= 4 | AUTH_AUTH0_SECRET= 5 | AUTH_AUTH0_ISSUER= 6 | 7 | AUTH_FACEBOOK_ID= 8 | AUTH_FACEBOOK_SECRET= 9 | 10 | AUTH_GITHUB_ID= 11 | AUTH_GITHUB_SECRET= 12 | 13 | AUTH_GOOGLE_ID= 14 | AUTH_GOOGLE_SECRET= 15 | 16 | AUTH_TWITTER_ID= 17 | AUTH_TWITTER_SECRET= 18 | 19 | # THIRD_PARTY_API_EXAMPLE_BACKEND= # Read more at https://authjs.dev/guides/integrating-third-party-backends 20 | 21 | # AUTH_TRUST_HOST=1 # Read more at https://authjs.dev/getting-started/deployment#auth_trust_host -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/github/administering-a-repository/displaying-a-sponsor-button-in-your-repository 2 | 3 | open_collective: nextauth 4 | github: [balazsorban44, ThangHuuVu] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | node_modules/ 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | lerna-debug.log* 10 | .yarn-integrity 11 | .npm 12 | 13 | .eslintcache 14 | 15 | *.tsbuildinfo 16 | next-env.d.ts 17 | 18 | .next 19 | .vercel 20 | .env*.local -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | FROM node:20-alpine AS base 3 | 4 | # Install dependencies only when needed 5 | FROM base AS deps 6 | # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. 7 | RUN apk add --no-cache libc6-compat 8 | WORKDIR /app 9 | 10 | # Install dependencies 11 | COPY package.json pnpm-lock.yaml* ./ 12 | RUN corepack enable pnpm && pnpm i --frozen-lockfile 13 | 14 | # Rebuild the source code only when needed 15 | FROM base AS builder 16 | WORKDIR /app 17 | COPY --from=deps /app/node_modules ./node_modules 18 | COPY . . 19 | 20 | # Next.js collects completely anonymous telemetry data about general usage. 21 | # Learn more here: https://nextjs.org/telemetry 22 | # Uncomment the following line in case you want to disable telemetry during the build. 23 | # ENV NEXT_TELEMETRY_DISABLED 1 24 | 25 | RUN corepack enable pnpm && pnpm build 26 | 27 | # Production image, copy all the files and run next 28 | FROM base AS runner 29 | WORKDIR /app 30 | 31 | ENV NODE_ENV production 32 | # Uncomment the following line in case you want to disable telemetry during runtime. 33 | # ENV NEXT_TELEMETRY_DISABLED 1 34 | 35 | RUN addgroup --system --gid 1001 nodejs 36 | RUN adduser --system --uid 1001 nextjs 37 | 38 | COPY --from=builder /app/public ./public 39 | 40 | # Set the correct permission for prerender cache 41 | RUN mkdir .next 42 | RUN chown nextjs:nodejs .next 43 | 44 | # Automatically leverage output traces to reduce image size 45 | # https://nextjs.org/docs/advanced-features/output-file-tracing 46 | COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ 47 | COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static 48 | 49 | USER nextjs 50 | 51 | EXPOSE 3000 52 | 53 | ENV PORT 3000 54 | ENV HOSTNAME "0.0.0.0" 55 | 56 | # server.js is created by next build from the standalone output 57 | # https://nextjs.org/docs/pages/api-reference/next-config-js/output 58 | CMD ["node", "server.js"] 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2022-2024, Balázs Orbán 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > The example repository is maintained from a [monorepo](https://github.com/nextauthjs/next-auth/tree/main/apps/examples/nextjs). Pull Requests should be opened against [`nextauthjs/next-auth`](https://github.com/nextauthjs/next-auth). 2 | 3 |

4 |
5 | 6 |

NextAuth.js Example App

7 |

8 | Open Source. Full Stack. Own Your Data. 9 |

10 |

11 | 12 | npm 13 | 14 | 15 | Bundle Size 16 | 17 | 18 | Downloads 19 | 20 | 21 | TypeScript 22 | 23 |

24 |

25 | 26 | ## Overview 27 | 28 | NextAuth.js is a complete open source authentication solution. 29 | 30 | This is an example application that shows how `next-auth` is applied to a basic Next.js app. 31 | 32 | The deployed version can be found at [`next-auth-example.vercel.app`](https://next-auth-example.vercel.app) 33 | 34 | ### About NextAuth.js 35 | 36 | NextAuth.js is an easy to implement, full-stack (client/server) open source authentication library originally designed for [Next.js](https://nextjs.org) and [Serverless](https://vercel.com). Our goal is to [support even more frameworks](https://github.com/nextauthjs/next-auth/issues/2294) in the future. 37 | 38 | Go to [next-auth.js.org](https://authjs.dev) for more information and documentation. 39 | 40 | > _NextAuth.js is not officially associated with Vercel or Next.js._ 41 | 42 | ## Getting Started 43 | 44 | ### 1. Clone the repository and install dependencies 45 | 46 | ``` 47 | git clone https://github.com/nextauthjs/next-auth-example.git 48 | cd next-auth-example 49 | pnpm install 50 | ``` 51 | 52 | ### 2. Configure your local environment 53 | 54 | Copy the .env.local.example file in this directory to .env.local (which will be ignored by Git): 55 | 56 | ``` 57 | cp .env.local.example .env.local 58 | ``` 59 | 60 | Add details for one or more providers (e.g. Google, Twitter, GitHub, Email, etc). 61 | 62 | #### Database 63 | 64 | A database is needed to persist user accounts and to support email sign in. However, you can still use NextAuth.js for authentication without a database by using OAuth for authentication. If you do not specify a database, [JSON Web Tokens](https://jwt.io/introduction) will be enabled by default. 65 | 66 | You **can** skip configuring a database and come back to it later if you want. 67 | 68 | For more information about setting up a database, please check out the following links: 69 | 70 | - Docs: [authjs.dev/reference/core/adapters](https://authjs.dev/reference/core/adapters) 71 | 72 | ### 3. Configure Authentication Providers 73 | 74 | 1. Review and update options in `auth.ts` as needed. 75 | 76 | 2. When setting up OAuth, in the developer admin page for each of your OAuth services, you should configure the callback URL to use a callback path of `{server}/api/auth/callback/{provider}`. 77 | 78 | e.g. For Google OAuth you would use: `http://localhost:3000/api/auth/callback/google` 79 | 80 | A list of configured providers and their callback URLs is available from the endpoint `api/auth/providers`. You can find more information at https://authjs.dev/getting-started/providers/oauth-tutorial 81 | 82 | 1. You can also choose to specify an SMTP server for passwordless sign in via email. 83 | 84 | ### 4. Start the application 85 | 86 | To run your site locally, use: 87 | 88 | ``` 89 | pnpm run dev 90 | ``` 91 | 92 | To run it in production mode, use: 93 | 94 | ``` 95 | pnpm run build 96 | pnpm run start 97 | ``` 98 | 99 | ### 5. Preparing for Production 100 | 101 | Follow the [Deployment documentation](https://authjs.dev/getting-started/deployment) 102 | 103 | ## Acknowledgements 104 | 105 | 106 | Powered By Vercel 107 | 108 |

Thanks to Vercel sponsoring this project by allowing it to be deployed for free for the entire NextAuth.js Team

109 | 110 | ## License 111 | 112 | ISC 113 | -------------------------------------------------------------------------------- /app/[...proxy]/route.tsx: -------------------------------------------------------------------------------- 1 | import { auth } from "@/auth" 2 | import { NextRequest } from "next/server" 3 | 4 | // Review if we need this, and why 5 | function stripContentEncoding(result: Response) { 6 | const responseHeaders = new Headers(result.headers) 7 | responseHeaders.delete("content-encoding") 8 | 9 | return new Response(result.body, { 10 | status: result.status, 11 | statusText: result.statusText, 12 | headers: responseHeaders, 13 | }) 14 | } 15 | 16 | async function handler(request: NextRequest) { 17 | const session = await auth() 18 | 19 | const headers = new Headers(request.headers) 20 | headers.set("Authorization", `Bearer ${session?.accessToken}`) 21 | 22 | let backendUrl = 23 | process.env.THIRD_PARTY_API_EXAMPLE_BACKEND ?? 24 | "https://third-party-backend.authjs.dev" 25 | 26 | let url = request.nextUrl.href.replace(request.nextUrl.origin, backendUrl) 27 | let result = await fetch(url, { headers, body: request.body }) 28 | 29 | return stripContentEncoding(result) 30 | } 31 | 32 | export const dynamic = "force-dynamic" 33 | 34 | export { handler as GET, handler as POST } 35 | -------------------------------------------------------------------------------- /app/api-example/page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | import CustomLink from "@/components/custom-link" 3 | import { useEffect, useState } from "react" 4 | 5 | export default function Page() { 6 | const [data, setData] = useState() 7 | useEffect(() => { 8 | ;(async () => { 9 | const res = await fetch("/api/protected") 10 | const json = await res.json() 11 | setData(json) 12 | })() 13 | }, []) 14 | return ( 15 |
16 |

Route Handler Usage

17 |

18 | This page fetches data from an API{" "} 19 | 20 | Route Handler 21 | 22 | . The API is protected using the universal{" "} 23 | 24 | auth() 25 | {" "} 26 | method. 27 |

28 |
29 |
30 | Data from API Route 31 |
32 |
33 |           {JSON.stringify(data, null, 2)}
34 |         
35 |
36 |
37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /app/api/protected/route.ts: -------------------------------------------------------------------------------- 1 | import { auth } from "auth" 2 | 3 | export const GET = auth((req) => { 4 | if (req.auth) { 5 | return Response.json({ data: "Protected data" }) 6 | } 7 | 8 | return Response.json({ message: "Not authenticated" }, { status: 401 }) 9 | }) 10 | -------------------------------------------------------------------------------- /app/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import { handlers } from "auth" 2 | export const { GET, POST } = handlers 3 | -------------------------------------------------------------------------------- /app/client-example/page.tsx: -------------------------------------------------------------------------------- 1 | import { auth } from "auth" 2 | import ClientExample from "@/components/client-example" 3 | import { SessionProvider } from "next-auth/react" 4 | 5 | export default async function ClientPage() { 6 | const session = await auth() 7 | if (session?.user) { 8 | // TODO: Look into https://react.dev/reference/react/experimental_taintObjectReference 9 | // filter out sensitive data before passing to client. 10 | session.user = { 11 | name: session.user.name, 12 | email: session.user.email, 13 | image: session.user.image, 14 | } 15 | } 16 | 17 | return ( 18 | 19 | 20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nextauthjs/next-auth-example/f2754cf5f37f100e126284702fbd1e6cc75e5afa/app/favicon.ico -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | 10 | --card: 0 0% 100%; 11 | --card-foreground: 222.2 84% 4.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 222.2 84% 4.9%; 15 | 16 | --primary: 222.2 47.4% 11.2%; 17 | --primary-foreground: 210 40% 98%; 18 | 19 | --secondary: 210 40% 96.1%; 20 | --secondary-foreground: 222.2 47.4% 11.2%; 21 | 22 | --muted: 210 40% 96.1%; 23 | --muted-foreground: 215.4 16.3% 46.9%; 24 | 25 | --accent: 210 40% 96.1%; 26 | --accent-foreground: 222.2 47.4% 11.2%; 27 | 28 | --destructive: 0 84.2% 60.2%; 29 | --destructive-foreground: 210 40% 98%; 30 | 31 | --border: 214.3 31.8% 91.4%; 32 | --input: 214.3 31.8% 91.4%; 33 | --ring: 222.2 84% 4.9%; 34 | 35 | --radius: 0.5rem; 36 | } 37 | } 38 | 39 | @layer base { 40 | * { 41 | @apply border-border; 42 | } 43 | 44 | body { 45 | @apply bg-background text-foreground; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "./globals.css" 2 | import type { Metadata } from "next" 3 | import { Inter } from "next/font/google" 4 | import Footer from "@/components/footer" 5 | import Header from "@/components/header" 6 | 7 | const inter = Inter({ subsets: ["latin"] }) 8 | 9 | export const metadata: Metadata = { 10 | title: "NextAuth.js Example", 11 | description: 12 | "This is an example site to demonstrate how to use NextAuth.js for authentication", 13 | } 14 | 15 | export default function RootLayout({ children }: React.PropsWithChildren) { 16 | return ( 17 | 18 | 19 |
20 |
21 |
22 | {children} 23 |
24 |
26 | 27 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /app/middleware-example/page.tsx: -------------------------------------------------------------------------------- 1 | import CustomLink from "@/components/custom-link" 2 | 3 | export default function Page() { 4 | return ( 5 |
6 |

Middleware usage

7 |

8 | This page is protected by using the universal{" "} 9 | 10 | auth() 11 | {" "} 12 | method in{" "} 13 | 14 | Next.js Middleware 15 | 16 | . 17 |

18 |
19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import CustomLink from "@/components/custom-link" 2 | import { auth } from "auth" 3 | 4 | export default async function Index() { 5 | const session = await auth() 6 | 7 | return ( 8 |
9 |

NextAuth.js Example

10 |
11 | This is an example site to demonstrate how to use{" "} 12 | NextAuth.js{" "} 13 | for authentication. Check out the{" "} 14 | 15 | Server 16 | {" "} 17 | and the{" "} 18 | 19 | Client 20 | {" "} 21 | examples to see how to secure pages and get session data. 22 |
23 |
24 | WebAuthn users are reset on every deploy, don't expect your test user(s) 25 | to still be available after a few days. It is designed to only 26 | demonstrate registration, login, and logout briefly. 27 |
28 |
29 |
30 | Current Session 31 |
32 |
33 |           {JSON.stringify(session, null, 2)}
34 |         
35 |
36 |
37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /app/policy/page.tsx: -------------------------------------------------------------------------------- 1 | export default function PolicyPage() { 2 | return ( 3 |
4 |
5 |

Terms of Service

6 |

7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 8 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 9 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 10 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 11 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 12 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 13 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 |

15 |
16 |
17 |

Privacy Policy

18 |

19 | This site uses JSON Web Tokens and a Key-Value database for sessions 20 | and WebAuthn authenticators which resets every 2 hours. 21 |

22 |

23 | Data provided to this site is exclusively used to support signing in 24 | and is not passed to any third party services, other than via SMTP or 25 | OAuth for the purposes of authentication. And Vercel KV / Upstash for 26 | hosting the Key Value store. This data is deleted every 2 hours via 27 | cron job. 28 |

29 |
30 |
31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /app/server-example/page.tsx: -------------------------------------------------------------------------------- 1 | import CustomLink from "@/components/custom-link" 2 | import SessionData from "@/components/session-data" 3 | import { auth } from "auth" 4 | 5 | export default async function Page() { 6 | const session = await auth() 7 | return ( 8 |
9 |

React Server Component Usage

10 |

11 | This page is server-rendered as a{" "} 12 | 13 | React Server Component 14 | 15 | . It gets the session data on the server using{" "} 16 | 17 | auth() 18 | {" "} 19 | method. 20 |

21 | 22 |
23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /auth.ts: -------------------------------------------------------------------------------- 1 | import NextAuth from "next-auth" 2 | import "next-auth/jwt" 3 | 4 | import Apple from "next-auth/providers/apple" 5 | // import Atlassian from "next-auth/providers/atlassian" 6 | import Auth0 from "next-auth/providers/auth0" 7 | import AzureB2C from "next-auth/providers/azure-ad-b2c" 8 | import BankIDNorway from "next-auth/providers/bankid-no" 9 | import BoxyHQSAML from "next-auth/providers/boxyhq-saml" 10 | import Cognito from "next-auth/providers/cognito" 11 | import Coinbase from "next-auth/providers/coinbase" 12 | import Discord from "next-auth/providers/discord" 13 | import Dropbox from "next-auth/providers/dropbox" 14 | import Facebook from "next-auth/providers/facebook" 15 | import GitHub from "next-auth/providers/github" 16 | import GitLab from "next-auth/providers/gitlab" 17 | import Google from "next-auth/providers/google" 18 | import Hubspot from "next-auth/providers/hubspot" 19 | import Keycloak from "next-auth/providers/keycloak" 20 | import LinkedIn from "next-auth/providers/linkedin" 21 | import MicrosoftEntraId from "next-auth/providers/microsoft-entra-id" 22 | import Netlify from "next-auth/providers/netlify" 23 | import Okta from "next-auth/providers/okta" 24 | import Passage from "next-auth/providers/passage" 25 | import Passkey from "next-auth/providers/passkey" 26 | import Pinterest from "next-auth/providers/pinterest" 27 | import Reddit from "next-auth/providers/reddit" 28 | import Slack from "next-auth/providers/slack" 29 | import Salesforce from "next-auth/providers/salesforce" 30 | import Spotify from "next-auth/providers/spotify" 31 | import Twitch from "next-auth/providers/twitch" 32 | import Twitter from "next-auth/providers/twitter" 33 | import Vipps from "next-auth/providers/vipps" 34 | import WorkOS from "next-auth/providers/workos" 35 | import Zoom from "next-auth/providers/zoom" 36 | import { createStorage } from "unstorage" 37 | import memoryDriver from "unstorage/drivers/memory" 38 | import vercelKVDriver from "unstorage/drivers/vercel-kv" 39 | import { UnstorageAdapter } from "@auth/unstorage-adapter" 40 | 41 | const storage = createStorage({ 42 | driver: process.env.VERCEL 43 | ? vercelKVDriver({ 44 | url: process.env.AUTH_KV_REST_API_URL, 45 | token: process.env.AUTH_KV_REST_API_TOKEN, 46 | env: false, 47 | }) 48 | : memoryDriver(), 49 | }) 50 | 51 | export const { handlers, auth, signIn, signOut } = NextAuth({ 52 | debug: !!process.env.AUTH_DEBUG, 53 | theme: { logo: "https://authjs.dev/img/logo-sm.png" }, 54 | adapter: UnstorageAdapter(storage), 55 | providers: [ 56 | Apple, 57 | // Atlassian, 58 | Auth0, 59 | AzureB2C, 60 | BankIDNorway, 61 | BoxyHQSAML({ 62 | clientId: "dummy", 63 | clientSecret: "dummy", 64 | issuer: process.env.AUTH_BOXYHQ_SAML_ISSUER, 65 | }), 66 | Cognito, 67 | Coinbase, 68 | Discord, 69 | Dropbox, 70 | Facebook, 71 | GitHub, 72 | GitLab, 73 | Google, 74 | Hubspot, 75 | Keycloak({ name: "Keycloak (bob/bob)" }), 76 | LinkedIn, 77 | MicrosoftEntraId, 78 | Netlify, 79 | Okta, 80 | Passkey({ 81 | formFields: { 82 | email: { 83 | label: "Username", 84 | required: true, 85 | autocomplete: "username webauthn", 86 | }, 87 | }, 88 | }), 89 | Passage, 90 | Pinterest, 91 | Reddit, 92 | Salesforce, 93 | Slack, 94 | Spotify, 95 | Twitch, 96 | Twitter, 97 | Vipps({ 98 | issuer: "https://apitest.vipps.no/access-management-1.0/access/", 99 | }), 100 | WorkOS({ connection: process.env.AUTH_WORKOS_CONNECTION! }), 101 | Zoom, 102 | ], 103 | basePath: "/auth", 104 | session: { strategy: "jwt" }, 105 | callbacks: { 106 | authorized({ request, auth }) { 107 | const { pathname } = request.nextUrl 108 | if (pathname === "/middleware-example") return !!auth 109 | return true 110 | }, 111 | jwt({ token, trigger, session, account }) { 112 | if (trigger === "update") token.name = session.user.name 113 | if (account?.provider === "keycloak") { 114 | return { ...token, accessToken: account.access_token } 115 | } 116 | return token 117 | }, 118 | async session({ session, token }) { 119 | if (token?.accessToken) session.accessToken = token.accessToken 120 | 121 | return session 122 | }, 123 | }, 124 | experimental: { enableWebAuthn: true }, 125 | }) 126 | 127 | declare module "next-auth" { 128 | interface Session { 129 | accessToken?: string 130 | } 131 | } 132 | 133 | declare module "next-auth/jwt" { 134 | interface JWT { 135 | accessToken?: string 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /components/access-denied.tsx: -------------------------------------------------------------------------------- 1 | import { signIn } from "next-auth/react" 2 | 3 | export default function AccessDenied() { 4 | return ( 5 | <> 6 |

Access Denied

7 |

8 | { 11 | e.preventDefault() 12 | signIn() 13 | }} 14 | > 15 | You must be signed in to view this page 16 | 17 |

18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /components/auth-components.tsx: -------------------------------------------------------------------------------- 1 | import { signIn, signOut } from "auth" 2 | import { Button } from "./ui/button" 3 | 4 | export function SignIn({ 5 | provider, 6 | ...props 7 | }: { provider?: string } & React.ComponentPropsWithRef) { 8 | return ( 9 |
{ 11 | "use server" 12 | await signIn(provider) 13 | }} 14 | > 15 | 16 |
17 | ) 18 | } 19 | 20 | export function SignOut(props: React.ComponentPropsWithRef) { 21 | return ( 22 |
{ 24 | "use server" 25 | await signOut() 26 | }} 27 | className="w-full" 28 | > 29 | 32 |
33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /components/client-example.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useSession } from "next-auth/react" 4 | import { Button } from "./ui/button" 5 | import { Input } from "./ui/input" 6 | import { useState } from "react" 7 | import SessionData from "./session-data" 8 | import CustomLink from "./custom-link" 9 | 10 | const UpdateForm = () => { 11 | const { data: session, update } = useSession() 12 | const [name, setName] = useState(`New ${session?.user?.name}`) 13 | 14 | if (!session?.user) return null 15 | return ( 16 | <> 17 |

Updating the session client-side

18 |
19 | { 24 | setName(e.target.value) 25 | }} 26 | /> 27 | 30 |
31 | 32 | ) 33 | } 34 | 35 | export default function ClientExample() { 36 | const { data: session, status } = useSession() 37 | const [apiResponse, setApiResponse] = useState("") 38 | 39 | const makeRequestWithToken = async () => { 40 | try { 41 | const response = await fetch("/api/authenticated/greeting") 42 | const data = await response.json() 43 | setApiResponse(JSON.stringify(data, null, 2)) 44 | } catch (error) { 45 | setApiResponse("Failed to fetch data: " + error) 46 | } 47 | } 48 | 49 | return ( 50 |
51 |

Client Side Rendering

52 |

53 | This page fetches session data client side using the{" "} 54 | 55 | useSession 56 | {" "} 57 | React Hook. 58 |

59 |

60 | It needs the{" "} 61 | 62 | 'use client' 63 | {" "} 64 | directive at the top of the file to enable client side rendering, and 65 | the{" "} 66 | 67 | SessionProvider 68 | {" "} 69 | component in{" "} 70 | 71 | client-example/page.tsx 72 | {" "} 73 | to provide the session data. 74 |

75 | 76 |
77 |

Third-party backend integration

78 |

79 | Press the button to send a request to our{" "} 80 | 81 | example backend 82 | 83 | . Read more{" "} 84 | 85 | here 86 | 87 |

88 |
89 | 95 |
96 |
{apiResponse}
97 |

98 | Note: This example only works when using the Keycloak provider. 99 |

100 |
101 | 102 | {status === "loading" ? ( 103 |
Loading...
104 | ) : ( 105 | 106 | )} 107 | 108 |
109 | ) 110 | } 111 | -------------------------------------------------------------------------------- /components/custom-link.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | import { ExternalLink } from "lucide-react" 3 | import Link from "next/link" 4 | 5 | interface CustomLinkProps extends React.LinkHTMLAttributes { 6 | href: string 7 | } 8 | 9 | const CustomLink = ({ 10 | href, 11 | children, 12 | className, 13 | ...rest 14 | }: CustomLinkProps) => { 15 | const isInternalLink = href.startsWith("/") 16 | const isAnchorLink = href.startsWith("#") 17 | 18 | if (isInternalLink || isAnchorLink) { 19 | return ( 20 | 21 | {children} 22 | 23 | ) 24 | } 25 | 26 | return ( 27 | 37 | {children} 38 | 39 | 40 | ) 41 | } 42 | 43 | export default CustomLink 44 | -------------------------------------------------------------------------------- /components/footer.module.css: -------------------------------------------------------------------------------- 1 | .footer { 2 | margin-top: 2rem; 3 | } 4 | 5 | .navItems { 6 | margin-bottom: 1rem; 7 | padding: 0; 8 | list-style: none; 9 | } 10 | 11 | .navItem { 12 | display: inline-block; 13 | margin-right: 1rem; 14 | } 15 | -------------------------------------------------------------------------------- /components/footer.tsx: -------------------------------------------------------------------------------- 1 | import CustomLink from "./custom-link" 2 | import packageJSON from "next-auth/package.json" 3 | 4 | export default function Footer() { 5 | return ( 6 |
7 |
8 | Documentation 9 | 10 | NPM 11 | 12 | 13 | Source on GitHub 14 | 15 | Policy 16 |
17 |
18 | Auth.js Logo 23 | 24 | {packageJSON.version} 25 | 26 |
27 |
28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /components/header.module.css: -------------------------------------------------------------------------------- 1 | /* Set min-height to avoid page reflow while session loading */ 2 | .signedInStatus { 3 | display: block; 4 | min-height: 4rem; 5 | width: 100%; 6 | } 7 | 8 | .loading, 9 | .loaded { 10 | position: relative; 11 | top: 0; 12 | opacity: 1; 13 | overflow: hidden; 14 | border-radius: 0 0 0.6rem 0.6rem; 15 | padding: 0.6rem 1rem; 16 | margin: 0; 17 | background-color: rgba(0, 0, 0, 0.05); 18 | transition: all 0.2s ease-in; 19 | } 20 | 21 | .loading { 22 | top: -2rem; 23 | opacity: 0; 24 | } 25 | 26 | .signedInText, 27 | .notSignedInText { 28 | position: absolute; 29 | padding-top: 0.8rem; 30 | left: 1rem; 31 | right: 6.5rem; 32 | white-space: nowrap; 33 | text-overflow: ellipsis; 34 | overflow: hidden; 35 | display: inherit; 36 | z-index: 1; 37 | line-height: 1.3rem; 38 | } 39 | 40 | .signedInText { 41 | padding-top: 0rem; 42 | left: 4.6rem; 43 | } 44 | 45 | .avatar { 46 | border-radius: 2rem; 47 | float: left; 48 | height: 2.8rem; 49 | width: 2.8rem; 50 | background-color: white; 51 | background-size: cover; 52 | background-repeat: no-repeat; 53 | } 54 | 55 | .button, 56 | .buttonPrimary { 57 | float: right; 58 | margin-right: -0.4rem; 59 | font-weight: 500; 60 | border-radius: 0.3rem; 61 | cursor: pointer; 62 | font-size: 1rem; 63 | line-height: 1.4rem; 64 | padding: 0.7rem 0.8rem; 65 | position: relative; 66 | z-index: 10; 67 | background-color: transparent; 68 | color: #555; 69 | } 70 | 71 | .buttonPrimary { 72 | background-color: #346df1; 73 | border-color: #346df1; 74 | color: #fff; 75 | text-decoration: none; 76 | padding: 0.7rem 1.4rem; 77 | } 78 | 79 | .buttonPrimary:hover { 80 | box-shadow: inset 0 0 5rem rgba(0, 0, 0, 0.2); 81 | } 82 | 83 | .navItems { 84 | margin-bottom: 2rem; 85 | padding: 0; 86 | list-style: none; 87 | } 88 | 89 | .navItem { 90 | display: inline-block; 91 | margin-right: 1rem; 92 | } 93 | -------------------------------------------------------------------------------- /components/header.tsx: -------------------------------------------------------------------------------- 1 | import { MainNav } from "./main-nav" 2 | import UserButton from "./user-button" 3 | 4 | export default function Header() { 5 | return ( 6 |
7 |
8 | 9 | 10 |
11 |
12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /components/layout.tsx: -------------------------------------------------------------------------------- 1 | import Header from "./header" 2 | import Footer from "./footer" 3 | import type { ReactNode } from "react" 4 | 5 | export default function Layout({ children }: { children: ReactNode }) { 6 | return ( 7 | <> 8 |
9 |
{children}
10 |