├── .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 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
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 |
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 |
25 |
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 |
17 | )
18 | }
19 |
20 | export function SignOut(props: React.ComponentPropsWithRef) {
21 | return (
22 |
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 | update({ user: { name } })} type="submit">
28 | Update
29 |
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 |
93 | Make API Request
94 |
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 |
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 |
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 |
11 | >
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/components/main-nav.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import Image from "next/image"
4 |
5 | import { cn } from "@/lib/utils"
6 | import CustomLink from "./custom-link"
7 | import {
8 | NavigationMenu,
9 | NavigationMenuContent,
10 | NavigationMenuItem,
11 | NavigationMenuLink,
12 | NavigationMenuList,
13 | NavigationMenuTrigger,
14 | navigationMenuTriggerStyle,
15 | } from "./ui/navigation-menu"
16 | import React from "react"
17 | import { Button } from "./ui/button"
18 |
19 | export function MainNav() {
20 | return (
21 |
22 |
23 |
24 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | Server Side
38 |
39 |
40 |
41 |
42 | Protecting React Server Component.
43 |
44 |
45 | Using Middleware to protect pages & APIs.
46 |
47 |
48 | Getting the session inside an API Route.
49 |
50 |
51 |
52 |
53 |
54 |
58 | Client Side
59 |
60 |
61 |
62 |
63 |
64 | )
65 | }
66 |
67 | const ListItem = React.forwardRef<
68 | React.ElementRef<"a">,
69 | React.ComponentPropsWithoutRef<"a">
70 | >(({ className, title, children, ...props }, ref) => {
71 | return (
72 |
73 |
74 |
82 | {title}
83 |
84 | {children}
85 |
86 |
87 |
88 |
89 | )
90 | })
91 | ListItem.displayName = "ListItem"
92 |
--------------------------------------------------------------------------------
/components/session-data.tsx:
--------------------------------------------------------------------------------
1 | import type { Session } from "next-auth"
2 |
3 | export default function SessionData({ session }: { session: Session | null }) {
4 | if (session?.user) {
5 | return (
6 |
7 |
Current Session Data
8 | {Object.keys(session.user).length > 3 ? (
9 |
10 | In this example, the whole session object is passed to the page,
11 | including the raw user object. Our recommendation is to{" "}
12 | only pass the necessary fields to the page, as the raw user
13 | object may contain sensitive information.
14 |
15 | ) : (
16 |
17 | In this example, only some fields in the user object is passed to
18 | the page to avoid exposing sensitive information.
19 |
20 | )}
21 |
22 |
23 | Session
24 |
25 |
26 | {JSON.stringify(session, null, 2)}
27 |
28 |
29 |
30 | )
31 | }
32 |
33 | return (
34 |
35 | No session data, please Sign In first.
36 |
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AvatarPrimitive from "@radix-ui/react-avatar"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Avatar = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ))
21 | Avatar.displayName = AvatarPrimitive.Root.displayName
22 |
23 | const AvatarImage = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
32 | ))
33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName
34 |
35 | const AvatarFallback = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 | ))
48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
49 |
50 | export { Avatar, AvatarImage, AvatarFallback }
51 |
--------------------------------------------------------------------------------
/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | )
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : "button"
45 | return (
46 |
51 | )
52 | }
53 | )
54 | Button.displayName = "Button"
55 |
56 | export { Button, buttonVariants }
57 |
--------------------------------------------------------------------------------
/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5 | import { Check, ChevronRight, Circle } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const DropdownMenu = DropdownMenuPrimitive.Root
10 |
11 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
12 |
13 | const DropdownMenuGroup = DropdownMenuPrimitive.Group
14 |
15 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal
16 |
17 | const DropdownMenuSub = DropdownMenuPrimitive.Sub
18 |
19 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
20 |
21 | const DropdownMenuSubTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef & {
24 | inset?: boolean
25 | }
26 | >(({ className, inset, children, ...props }, ref) => (
27 |
36 | {children}
37 |
38 |
39 | ))
40 | DropdownMenuSubTrigger.displayName =
41 | DropdownMenuPrimitive.SubTrigger.displayName
42 |
43 | const DropdownMenuSubContent = React.forwardRef<
44 | React.ElementRef,
45 | React.ComponentPropsWithoutRef
46 | >(({ className, ...props }, ref) => (
47 |
55 | ))
56 | DropdownMenuSubContent.displayName =
57 | DropdownMenuPrimitive.SubContent.displayName
58 |
59 | const DropdownMenuContent = React.forwardRef<
60 | React.ElementRef,
61 | React.ComponentPropsWithoutRef
62 | >(({ className, sideOffset = 4, ...props }, ref) => (
63 |
64 |
73 |
74 | ))
75 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
76 |
77 | const DropdownMenuItem = React.forwardRef<
78 | React.ElementRef,
79 | React.ComponentPropsWithoutRef & {
80 | inset?: boolean
81 | }
82 | >(({ className, inset, ...props }, ref) => (
83 |
92 | ))
93 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
94 |
95 | const DropdownMenuCheckboxItem = React.forwardRef<
96 | React.ElementRef,
97 | React.ComponentPropsWithoutRef
98 | >(({ className, children, checked, ...props }, ref) => (
99 |
108 |
109 |
110 |
111 |
112 |
113 | {children}
114 |
115 | ))
116 | DropdownMenuCheckboxItem.displayName =
117 | DropdownMenuPrimitive.CheckboxItem.displayName
118 |
119 | const DropdownMenuRadioItem = React.forwardRef<
120 | React.ElementRef,
121 | React.ComponentPropsWithoutRef
122 | >(({ className, children, ...props }, ref) => (
123 |
131 |
132 |
133 |
134 |
135 |
136 | {children}
137 |
138 | ))
139 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
140 |
141 | const DropdownMenuLabel = React.forwardRef<
142 | React.ElementRef,
143 | React.ComponentPropsWithoutRef & {
144 | inset?: boolean
145 | }
146 | >(({ className, inset, ...props }, ref) => (
147 |
156 | ))
157 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
158 |
159 | const DropdownMenuSeparator = React.forwardRef<
160 | React.ElementRef,
161 | React.ComponentPropsWithoutRef
162 | >(({ className, ...props }, ref) => (
163 |
168 | ))
169 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
170 |
171 | const DropdownMenuShortcut = ({
172 | className,
173 | ...props
174 | }: React.HTMLAttributes) => {
175 | return (
176 |
180 | )
181 | }
182 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
183 |
184 | export {
185 | DropdownMenu,
186 | DropdownMenuTrigger,
187 | DropdownMenuContent,
188 | DropdownMenuItem,
189 | DropdownMenuCheckboxItem,
190 | DropdownMenuRadioItem,
191 | DropdownMenuLabel,
192 | DropdownMenuSeparator,
193 | DropdownMenuShortcut,
194 | DropdownMenuGroup,
195 | DropdownMenuPortal,
196 | DropdownMenuSub,
197 | DropdownMenuSubContent,
198 | DropdownMenuSubTrigger,
199 | DropdownMenuRadioGroup,
200 | }
201 |
--------------------------------------------------------------------------------
/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/components/ui/navigation-menu.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
3 | import { cva } from "class-variance-authority"
4 | import { ChevronDown } from "lucide-react"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const NavigationMenu = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
20 | {children}
21 |
22 |
23 | ))
24 | NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
25 |
26 | const NavigationMenuList = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, ...props }, ref) => (
30 |
38 | ))
39 | NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
40 |
41 | const NavigationMenuItem = NavigationMenuPrimitive.Item
42 |
43 | const navigationMenuTriggerStyle = cva(
44 | "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-2 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
45 | )
46 |
47 | const NavigationMenuTrigger = React.forwardRef<
48 | React.ElementRef,
49 | React.ComponentPropsWithoutRef
50 | >(({ className, children, ...props }, ref) => (
51 |
56 | {children}{" "}
57 |
61 |
62 | ))
63 | NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
64 |
65 | const NavigationMenuContent = React.forwardRef<
66 | React.ElementRef,
67 | React.ComponentPropsWithoutRef
68 | >(({ className, ...props }, ref) => (
69 |
77 | ))
78 | NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
79 |
80 | const NavigationMenuLink = NavigationMenuPrimitive.Link
81 |
82 | const NavigationMenuViewport = React.forwardRef<
83 | React.ElementRef,
84 | React.ComponentPropsWithoutRef
85 | >(({ className, ...props }, ref) => (
86 |
87 |
95 |
96 | ))
97 | NavigationMenuViewport.displayName =
98 | NavigationMenuPrimitive.Viewport.displayName
99 |
100 | const NavigationMenuIndicator = React.forwardRef<
101 | React.ElementRef,
102 | React.ComponentPropsWithoutRef
103 | >(({ className, ...props }, ref) => (
104 |
112 |
113 |
114 | ))
115 | NavigationMenuIndicator.displayName =
116 | NavigationMenuPrimitive.Indicator.displayName
117 |
118 | export {
119 | navigationMenuTriggerStyle,
120 | NavigationMenu,
121 | NavigationMenuList,
122 | NavigationMenuItem,
123 | NavigationMenuContent,
124 | NavigationMenuTrigger,
125 | NavigationMenuLink,
126 | NavigationMenuIndicator,
127 | NavigationMenuViewport,
128 | }
129 |
--------------------------------------------------------------------------------
/components/user-button.tsx:
--------------------------------------------------------------------------------
1 | import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar"
2 | import { Button } from "./ui/button"
3 | import { auth } from "auth"
4 | import {
5 | DropdownMenu,
6 | DropdownMenuContent,
7 | DropdownMenuItem,
8 | DropdownMenuLabel,
9 | DropdownMenuTrigger,
10 | } from "./ui/dropdown-menu"
11 | import { SignIn, SignOut } from "./auth-components"
12 |
13 | export default async function UserButton() {
14 | const session = await auth()
15 | if (!session?.user) return
16 | return (
17 |
18 |
19 | {session.user.email}
20 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | {session.user.name}
40 |
41 |
42 | {session.user.email}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | )
53 | }
54 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | authjs-docker-test:
3 | build: .
4 | environment:
5 | - TEST_KEYCLOAK_USERNAME
6 | - TEST_KEYCLOAK_PASSWORD
7 | - AUTH_KEYCLOAK_ID
8 | - AUTH_KEYCLOAK_SECRET
9 | - AUTH_KEYCLOAK_ISSUER
10 | - AUTH_SECRET="MohY0/2zSQw/psWEnejC2ka3Al0oifvY4YjOkUaFfnI="
11 | - AUTH_URL=http://localhost:3000/auth
12 | ports:
13 | - "3000:3000"
14 |
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/middleware.ts:
--------------------------------------------------------------------------------
1 | export { auth as middleware } from "auth"
2 |
3 | // Or like this if you need to do something here.
4 | // export default auth((req) => {
5 | // console.log(req.auth) // { session: { user: { ... } } }
6 | // })
7 |
8 | // Read more: https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
9 | export const config = {
10 | matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
11 | }
12 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import("next").NextConfig} */
2 | module.exports = {
3 | output: "standalone",
4 | }
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "description": "An example project for NextAuth.js with Next.js",
4 | "repository": "https://github.com/nextauthjs/next-auth-example.git",
5 | "bugs": {
6 | "url": "https://github.com/nextauthjs/next-auth/issues"
7 | },
8 | "homepage": "https://next-auth-example.vercel.app",
9 | "scripts": {
10 | "dev": "next",
11 | "build": "next build",
12 | "start": "next start"
13 | },
14 | "author": "Iain Collins ",
15 | "contributors": [
16 | "Balázs Orbán ",
17 | "Nico Domino ",
18 | "Lluis Agusti ",
19 | "Thang Huu Vu "
20 | ],
21 | "dependencies": {
22 | "@auth/unstorage-adapter": "^2.0.0",
23 | "@radix-ui/react-avatar": "^1.0.3",
24 | "@radix-ui/react-collapsible": "^1.0.3",
25 | "@radix-ui/react-dropdown-menu": "^2.0.5",
26 | "@radix-ui/react-navigation-menu": "^1.1.3",
27 | "@radix-ui/react-slot": "^1.0.2",
28 | "@simplewebauthn/server": "^9.0.3",
29 | "@vercel/kv": "^1.0.1",
30 | "class-variance-authority": "^0.7.0",
31 | "clsx": "^2.0.0",
32 | "lucide-react": "^0.274.0",
33 | "next": "latest",
34 | "next-auth": "beta",
35 | "react": "^18.2.0",
36 | "react-dom": "^18.2.0",
37 | "tailwind-merge": "^1.14.0",
38 | "tailwindcss-animate": "^1.0.7",
39 | "unstorage": "^1.10.1"
40 | },
41 | "devDependencies": {
42 | "@types/node": "^20.12.8",
43 | "@types/react": "^18.2.23",
44 | "@types/react-dom": "^18.2.8",
45 | "autoprefixer": "^10.4.15",
46 | "postcss": "^8.4.29",
47 | "tailwindcss": "^3.3.3",
48 | "typescript": "^5.2.2"
49 | },
50 | "engines": {
51 | "node": ">=20.0.0"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nextauthjs/next-auth-example/f2754cf5f37f100e126284702fbd1e6cc75e5afa/public/logo.png
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | darkMode: "selector",
4 | content: [
5 | "./pages/**/*.{ts,tsx}",
6 | "./components/**/*.{ts,tsx}",
7 | "./app/**/*.{ts,tsx}",
8 | "./src/**/*.{ts,tsx}",
9 | ],
10 | theme: {
11 | container: {
12 | center: true,
13 | padding: "2rem",
14 | screens: {
15 | "2xl": "1400px",
16 | },
17 | },
18 | extend: {
19 | colors: {
20 | border: "hsl(var(--border))",
21 | input: "hsl(var(--input))",
22 | ring: "hsl(var(--ring))",
23 | background: "hsl(var(--background))",
24 | foreground: "hsl(var(--foreground))",
25 | primary: {
26 | DEFAULT: "hsl(var(--primary))",
27 | foreground: "hsl(var(--primary-foreground))",
28 | },
29 | secondary: {
30 | DEFAULT: "hsl(var(--secondary))",
31 | foreground: "hsl(var(--secondary-foreground))",
32 | },
33 | destructive: {
34 | DEFAULT: "hsl(var(--destructive))",
35 | foreground: "hsl(var(--destructive-foreground))",
36 | },
37 | muted: {
38 | DEFAULT: "hsl(var(--muted))",
39 | foreground: "hsl(var(--muted-foreground))",
40 | },
41 | accent: {
42 | DEFAULT: "hsl(var(--accent))",
43 | foreground: "hsl(var(--accent-foreground))",
44 | },
45 | popover: {
46 | DEFAULT: "hsl(var(--popover))",
47 | foreground: "hsl(var(--popover-foreground))",
48 | },
49 | card: {
50 | DEFAULT: "hsl(var(--card))",
51 | foreground: "hsl(var(--card-foreground))",
52 | },
53 | },
54 | borderRadius: {
55 | lg: "var(--radius)",
56 | md: "calc(var(--radius) - 2px)",
57 | sm: "calc(var(--radius) - 4px)",
58 | },
59 | keyframes: {
60 | "accordion-down": {
61 | from: { height: 0 },
62 | to: { height: "var(--radix-accordion-content-height)" },
63 | },
64 | "accordion-up": {
65 | from: { height: "var(--radix-accordion-content-height)" },
66 | to: { height: 0 },
67 | },
68 | },
69 | animation: {
70 | "accordion-down": "accordion-down 0.2s ease-out",
71 | "accordion-up": "accordion-up 0.2s ease-out",
72 | },
73 | },
74 | },
75 | plugins: [require("tailwindcss-animate")],
76 | }
77 |
--------------------------------------------------------------------------------
/test-docker.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Easier to read `docker compose up` output
4 | # export BUILDKIT_PROGRESS=plain
5 |
6 | args=("-f" "docker-compose.yml")
7 | if [[ -z "${CI}" ]]; then
8 | args+=("--env-file" ".env")
9 | fi
10 | args+=("up" "--detach" "--build")
11 |
12 | echo "Running: docker compose ${args[*]}"
13 |
14 | if ! docker compose "${args[@]}"; then
15 | echo "Failed to start container"
16 | exit 1
17 | fi
18 |
19 | echo "waiting 10 seconds for container to start..."
20 | sleep 10
21 |
22 | # Used to control which env vars to load in the playwright process
23 | export TEST_DOCKER=1
24 |
25 | # Always stop container, but exit with 1 when tests are failing
26 | if playwright test -c ../../../packages/utils/playwright.config.ts; then
27 | docker compose down
28 | else
29 | docker compose down && exit 1
30 | fi
31 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "plugins": [
18 | {
19 | "name": "next"
20 | }
21 | ],
22 | "paths": {
23 | "@/*": ["./*"],
24 | "auth": ["./auth"]
25 | }
26 | },
27 | "include": [
28 | "process.d.ts",
29 | "next-env.d.ts",
30 | "**/*.ts",
31 | "**/*.tsx",
32 | ".next/types/**/*.ts"
33 | ],
34 | "exclude": ["node_modules"]
35 | }
36 |
--------------------------------------------------------------------------------