diff --git a/demos/next-auth/.env.local.example b/demos/next-auth/.env.local.example
new file mode 100644
index 0000000000..83cb4bc2d5
--- /dev/null
+++ b/demos/next-auth/.env.local.example
@@ -0,0 +1,4 @@
+NEXTAUTH_URL=http://localhost:3000
+
+NETLIFY_CLIENT_ID=
+NETLIFY_CLIENT_SECRET=
diff --git a/demos/next-auth/.eslintrc b/demos/next-auth/.eslintrc
new file mode 100644
index 0000000000..abd5579b49
--- /dev/null
+++ b/demos/next-auth/.eslintrc
@@ -0,0 +1,4 @@
+{
+ "extends": "next",
+ "root": true
+}
diff --git a/demos/next-auth/.gitignore b/demos/next-auth/.gitignore
new file mode 100644
index 0000000000..c6bba59138
--- /dev/null
+++ b/demos/next-auth/.gitignore
@@ -0,0 +1,130 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
diff --git a/demos/next-auth/LICENSE b/demos/next-auth/LICENSE
new file mode 100644
index 0000000000..eee418eafa
--- /dev/null
+++ b/demos/next-auth/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2018-2021, Iain Collins
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
\ No newline at end of file
diff --git a/demos/next-auth/README.md b/demos/next-auth/README.md
new file mode 100644
index 0000000000..c29a79f6f8
--- /dev/null
+++ b/demos/next-auth/README.md
@@ -0,0 +1,34 @@
+## NextAuth.js Example
+
+This example project came from [next-auth-example](https://github.com/nextauthjs/next-auth-example) provided by the NextAuth.js and was modified to use Netlify as the authentication provider as an example.
+
+For more details on how to get set up and configured with various providers, visit the link above.
+
+### Running locally
+
+Within this project directory:
+
+```
+npm install
+npm run dev
+```
+
+### Configuring your local environment
+
+Copy the .env.local.example file in this directory to .env.local (which will be ignored by Git):
+
+```
+cp .env.local.example .env.local
+```
+
+Add details for the [Netlify Provider](https://next-auth.js.org/providers/netlify), which will require create a Netlify OAuth application.
+
+To create a Netlify OAuth application:
+* Visit `https://app.netlify.com/user/applications`
+* Click 'New OAuth App'
+* Enter an application name
+* Enter a redirect URI (for the purposes of this demo application you would use `http://localhost:3000/api/auth/callback/netlify`)
+* Save the application, and copy the value for 'Client ID' as the `NETLIFY_CLIENT_ID` and the 'Client Secret' as the `NETLIFY_CLIENT_SECRET` into your `.env.local` file within the project
+ * If you're testing this on a deployed Netlify site you'll need to set the environment variables as part of the `Site Settings > Build & Deploy > Environment` settings. You'll also need to generate a `NEXTAUTH_SECRET` environment variable and set that for a production build.
+
+For configuring additional authentication providers, see the original documentation [here](https://github.com/nextauthjs/next-auth-example#3-configure-authentication-providers)
diff --git a/demos/next-auth/components/access-denied.tsx b/demos/next-auth/components/access-denied.tsx
new file mode 100644
index 0000000000..4c9c0d7947
--- /dev/null
+++ b/demos/next-auth/components/access-denied.tsx
@@ -0,0 +1,20 @@
+import { signIn } from "next-auth/react"
+
+export default function AccessDenied() {
+ return (
+ <>
+
Access Denied
+
+ {
+ e.preventDefault()
+ signIn()
+ }}
+ >
+ You must be signed in to view this page
+
+
+ >
+ )
+}
diff --git a/demos/next-auth/components/footer.module.css b/demos/next-auth/components/footer.module.css
new file mode 100644
index 0000000000..020bec6415
--- /dev/null
+++ b/demos/next-auth/components/footer.module.css
@@ -0,0 +1,14 @@
+.footer {
+ margin-top: 2rem;
+}
+
+.navItems {
+ margin-bottom: 1rem;
+ padding: 0;
+ list-style: none;
+}
+
+.navItem {
+ display: inline-block;
+ margin-right: 1rem;
+}
diff --git a/demos/next-auth/components/footer.tsx b/demos/next-auth/components/footer.tsx
new file mode 100644
index 0000000000..e01b64f198
--- /dev/null
+++ b/demos/next-auth/components/footer.tsx
@@ -0,0 +1,30 @@
+import Link from "next/link"
+import styles from "./footer.module.css"
+import packageJSON from "../package.json"
+
+export default function Footer() {
+ return (
+
+ )
+}
diff --git a/demos/next-auth/components/header.module.css b/demos/next-auth/components/header.module.css
new file mode 100644
index 0000000000..773478c6af
--- /dev/null
+++ b/demos/next-auth/components/header.module.css
@@ -0,0 +1,92 @@
+/* Set min-height to avoid page reflow while session loading */
+.signedInStatus {
+ display: block;
+ min-height: 4rem;
+ width: 100%;
+}
+
+.loading,
+.loaded {
+ position: relative;
+ top: 0;
+ opacity: 1;
+ overflow: hidden;
+ border-radius: 0 0 0.6rem 0.6rem;
+ padding: 0.6rem 1rem;
+ margin: 0;
+ background-color: rgba(0, 0, 0, 0.05);
+ transition: all 0.2s ease-in;
+}
+
+.loading {
+ top: -2rem;
+ opacity: 0;
+}
+
+.signedInText,
+.notSignedInText {
+ position: absolute;
+ padding-top: 0.8rem;
+ left: 1rem;
+ right: 6.5rem;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ display: inherit;
+ z-index: 1;
+ line-height: 1.3rem;
+}
+
+.signedInText {
+ padding-top: 0rem;
+ left: 4.6rem;
+}
+
+.avatar {
+ border-radius: 2rem;
+ float: left;
+ height: 2.8rem;
+ width: 2.8rem;
+ background-color: white;
+ background-size: cover;
+ background-repeat: no-repeat;
+}
+
+.button,
+.buttonPrimary {
+ float: right;
+ margin-right: -0.4rem;
+ font-weight: 500;
+ border-radius: 0.3rem;
+ cursor: pointer;
+ font-size: 1rem;
+ line-height: 1.4rem;
+ padding: 0.7rem 0.8rem;
+ position: relative;
+ z-index: 10;
+ background-color: transparent;
+ color: #555;
+}
+
+.buttonPrimary {
+ background-color: #346df1;
+ border-color: #346df1;
+ color: #fff;
+ text-decoration: none;
+ padding: 0.7rem 1.4rem;
+}
+
+.buttonPrimary:hover {
+ box-shadow: inset 0 0 5rem rgba(0, 0, 0, 0.2);
+}
+
+.navItems {
+ margin-bottom: 2rem;
+ padding: 0;
+ list-style: none;
+}
+
+.navItem {
+ display: inline-block;
+ margin-right: 1rem;
+}
diff --git a/demos/next-auth/components/header.tsx b/demos/next-auth/components/header.tsx
new file mode 100644
index 0000000000..8fed732d72
--- /dev/null
+++ b/demos/next-auth/components/header.tsx
@@ -0,0 +1,108 @@
+import Link from "next/link"
+import { signIn, signOut, useSession } from "next-auth/react"
+import styles from "./header.module.css"
+
+// The approach used in this component shows how to build a sign in and sign out
+// component that works on pages which support both client and server side
+// rendering, and avoids any flash incorrect content on initial page load.
+export default function Header() {
+ const { data: session, status } = useSession()
+ const loading = status === "loading"
+
+ return (
+
+ )
+}
diff --git a/demos/next-auth/components/layout.tsx b/demos/next-auth/components/layout.tsx
new file mode 100644
index 0000000000..8495690ad5
--- /dev/null
+++ b/demos/next-auth/components/layout.tsx
@@ -0,0 +1,17 @@
+import Header from "./header"
+import Footer from "./footer"
+import type { ReactChildren } from "react"
+
+interface Props {
+ children: React.ReactNode
+}
+
+export default function Layout({ children }: Props) {
+ return (
+ <>
+
+ {children}
+
+ >
+ )
+}
diff --git a/demos/next-auth/netlify.toml b/demos/next-auth/netlify.toml
new file mode 100644
index 0000000000..3878fcc1eb
--- /dev/null
+++ b/demos/next-auth/netlify.toml
@@ -0,0 +1,18 @@
+[build]
+command = "next build"
+publish = ".next"
+ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF . ../../plugin"
+
+[build.environment]
+TERM = "xterm"
+NODE_VERSION = "17"
+
+[dev]
+framework = "#static"
+
+[[plugins]]
+package = "../plugin-wrapper/"
+
+# This is a fake plugin, that makes it run npm install
+[[plugins]]
+package = "@netlify/plugin-local-install-core"
diff --git a/demos/next-auth/next-auth.d.ts b/demos/next-auth/next-auth.d.ts
new file mode 100644
index 0000000000..ce05151293
--- /dev/null
+++ b/demos/next-auth/next-auth.d.ts
@@ -0,0 +1,10 @@
+import "next-auth/jwt"
+
+// Read more at: https://next-auth.js.org/getting-started/typescript#module-augmentation
+
+declare module "next-auth/jwt" {
+ interface JWT {
+ /** The user's role. */
+ userRole?: "admin"
+ }
+}
diff --git a/demos/next-auth/next-env.d.ts b/demos/next-auth/next-env.d.ts
new file mode 100644
index 0000000000..4f11a03dc6
--- /dev/null
+++ b/demos/next-auth/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/demos/next-auth/package.json b/demos/next-auth/package.json
new file mode 100644
index 0000000000..7e7bac3f43
--- /dev/null
+++ b/demos/next-auth/package.json
@@ -0,0 +1,47 @@
+{
+ "name": "next-auth-demo",
+ "version": "0.0.0",
+ "private": true,
+ "description": "An example project using NextAuth.js",
+ "repository": "https://github.com/nextauthjs/next-auth-example.git",
+ "bugs": {
+ "url": "https://github.com/nextauthjs/next-auth/issues"
+ },
+ "homepage": "https://next-auth-example.vercel.app",
+ "main": "",
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "types": "tsc --noEmit"
+ },
+ "author": "Iain Collins ",
+ "contributors": [
+ "Balázs Orbán ",
+ "Nico Domino ",
+ "Lluis Agusti "
+ ],
+ "license": "ISC",
+ "dependencies": {
+ "next-auth": "latest",
+ "nodemailer": "^6.6.3",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2"
+ },
+ "devDependencies": {
+ "@netlify/plugin-nextjs": "*",
+ "@types/fs-extra": "^9.0.13",
+ "@types/jest": "^27.4.1",
+ "@types/node": "^17.0.14",
+ "@types/react": "^17.0.39",
+ "husky": "^7.0.4",
+ "npm-run-all": "^4.1.5",
+ "typescript": "^4.5.5"
+ },
+ "prettier": {
+ "semi": false
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+}
diff --git a/demos/next-auth/pages/_app.tsx b/demos/next-auth/pages/_app.tsx
new file mode 100644
index 0000000000..7d1e6b3fa0
--- /dev/null
+++ b/demos/next-auth/pages/_app.tsx
@@ -0,0 +1,13 @@
+import { SessionProvider } from "next-auth/react"
+import type { AppProps } from "next/app"
+import "./styles.css"
+
+// Use of the is mandatory to allow components that call
+// `useSession()` anywhere in your application to access the `session` object.
+export default function App({ Component, pageProps }: AppProps) {
+ return (
+
+
+
+ )
+}
diff --git a/demos/next-auth/pages/admin/_middleware.ts b/demos/next-auth/pages/admin/_middleware.ts
new file mode 100644
index 0000000000..ec07936088
--- /dev/null
+++ b/demos/next-auth/pages/admin/_middleware.ts
@@ -0,0 +1,8 @@
+import { withAuth } from "next-auth/middleware"
+
+// More on how NextAuth.js middleware works: https://next-auth.js.org/configuration/nextjs#middleware
+export default withAuth({
+ callbacks: {
+ authorized: ({ token }) => token?.userRole === "admin",
+ },
+})
diff --git a/demos/next-auth/pages/admin/index.tsx b/demos/next-auth/pages/admin/index.tsx
new file mode 100644
index 0000000000..e11d8a42a2
--- /dev/null
+++ b/demos/next-auth/pages/admin/index.tsx
@@ -0,0 +1,17 @@
+import Layout from "../../components/layout"
+
+export default function Page() {
+ return (
+
+ This page is protected by Middleware
+ Only admin users can see this page.
+
+ To learn more about the NextAuth middleware see
+
+ the docs
+
+ .
+
+
+ )
+}
diff --git a/demos/next-auth/pages/api-example.tsx b/demos/next-auth/pages/api-example.tsx
new file mode 100644
index 0000000000..1962311046
--- /dev/null
+++ b/demos/next-auth/pages/api-example.tsx
@@ -0,0 +1,19 @@
+import Layout from "../components/layout"
+
+export default function ApiExamplePage() {
+ return (
+
+ API Example
+ The examples below show responses from the example API endpoints.
+
+ You must be signed in to see responses.
+
+ Session
+ /api/examples/session
+
+ JSON Web Token
+ /api/examples/jwt
+
+
+ )
+}
diff --git a/demos/next-auth/pages/api/auth/[...nextauth].ts b/demos/next-auth/pages/api/auth/[...nextauth].ts
new file mode 100644
index 0000000000..310973a3dc
--- /dev/null
+++ b/demos/next-auth/pages/api/auth/[...nextauth].ts
@@ -0,0 +1,21 @@
+import NextAuth from "next-auth"
+import NetlifyProvider from "next-auth/providers/netlify"
+
+export default NextAuth({
+ providers: [
+ NetlifyProvider({
+ clientId: process.env.NETLIFY_CLIENT_ID,
+ clientSecret: process.env.NETLIFY_CLIENT_SECRET,
+ authorization: ({ params: { scope: "user" } })
+ })
+ ],
+ theme: {
+ colorScheme: "dark",
+ },
+ callbacks: {
+ async jwt({ token }) {
+ token.userRole = "admin"
+ return token
+ },
+ },
+})
diff --git a/demos/next-auth/pages/api/examples/jwt.ts b/demos/next-auth/pages/api/examples/jwt.ts
new file mode 100644
index 0000000000..46df88d455
--- /dev/null
+++ b/demos/next-auth/pages/api/examples/jwt.ts
@@ -0,0 +1,12 @@
+// This is an example of how to read a JSON Web Token from an API route
+import { getToken } from "next-auth/jwt"
+import type { NextApiRequest, NextApiResponse } from "next"
+
+const secret = process.env.NEXTAUTH_SECRET
+
+const jwt = async (req: NextApiRequest, res: NextApiResponse) => {
+ const token = await getToken({ req, secret })
+ res.send(JSON.stringify(token, null, 2))
+}
+
+export default jwt;
diff --git a/demos/next-auth/pages/api/examples/protected.ts b/demos/next-auth/pages/api/examples/protected.ts
new file mode 100644
index 0000000000..598cf72799
--- /dev/null
+++ b/demos/next-auth/pages/api/examples/protected.ts
@@ -0,0 +1,20 @@
+// This is an example of to protect an API route
+import { getSession } from "next-auth/react"
+import type { NextApiRequest, NextApiResponse } from "next"
+
+const protectedContentExample = async (req: NextApiRequest, res: NextApiResponse) => {
+ const session = await getSession({ req })
+
+ if (session) {
+ res.send({
+ content:
+ "This is protected content. You can access this content because you are signed in.",
+ })
+ } else {
+ res.send({
+ error: "You must be signed in to view the protected content on this page.",
+ })
+ }
+}
+
+export default protectedContentExample;
diff --git a/demos/next-auth/pages/api/examples/session.ts b/demos/next-auth/pages/api/examples/session.ts
new file mode 100644
index 0000000000..1ca5be3f92
--- /dev/null
+++ b/demos/next-auth/pages/api/examples/session.ts
@@ -0,0 +1,10 @@
+// This is an example of how to access a session from an API route
+import { getSession } from "next-auth/react"
+import type { NextApiRequest, NextApiResponse } from "next"
+
+const accessSessionExample = async (req: NextApiRequest, res: NextApiResponse) => {
+ const session = await getSession({ req })
+ res.send(JSON.stringify(session, null, 2))
+}
+
+export default accessSessionExample;
diff --git a/demos/next-auth/pages/client.tsx b/demos/next-auth/pages/client.tsx
new file mode 100644
index 0000000000..710a0051fb
--- /dev/null
+++ b/demos/next-auth/pages/client.tsx
@@ -0,0 +1,27 @@
+import Layout from "../components/layout"
+
+export default function ClientPage() {
+ return (
+
+ Client Side Rendering
+
+ This page uses the useSession() React Hook in the{" "}
+ <Header/> component.
+
+
+ The useSession() React Hook is easy to use and allows
+ pages to render very quickly.
+
+
+ The advantage of this approach is that session state is shared between
+ pages by using the Provider in _app.js{" "}
+ so that navigation between pages using useSession() is
+ very fast.
+
+
+ The disadvantage of useSession() is that it requires
+ client side JavaScript.
+
+
+ )
+}
diff --git a/demos/next-auth/pages/index.tsx b/demos/next-auth/pages/index.tsx
new file mode 100644
index 0000000000..7b09b8a306
--- /dev/null
+++ b/demos/next-auth/pages/index.tsx
@@ -0,0 +1,13 @@
+import Layout from "../components/layout"
+
+export default function IndexPage() {
+ return (
+
+ NextAuth.js Example
+
+ This is an example site to demonstrate how to use{" "}
+ NextAuth.js for authentication.
+
+
+ )
+}
diff --git a/demos/next-auth/pages/me/_middleware.ts b/demos/next-auth/pages/me/_middleware.ts
new file mode 100644
index 0000000000..0508ab7391
--- /dev/null
+++ b/demos/next-auth/pages/me/_middleware.ts
@@ -0,0 +1,2 @@
+// More on how NextAuth.js middleware works: https://next-auth.js.org/configuration/nextjs#middleware
+export { default } from "next-auth/middleware"
diff --git a/demos/next-auth/pages/me/index.tsx b/demos/next-auth/pages/me/index.tsx
new file mode 100644
index 0000000000..5674e49c51
--- /dev/null
+++ b/demos/next-auth/pages/me/index.tsx
@@ -0,0 +1,12 @@
+import { useSession } from "next-auth/react"
+import Layout from "../../components/layout"
+
+export default function MePage() {
+ const { data } = useSession()
+
+ return (
+
+ {JSON.stringify(data, null, 2)}
+
+ )
+}
diff --git a/demos/next-auth/pages/policy.tsx b/demos/next-auth/pages/policy.tsx
new file mode 100644
index 0000000000..18d26d99fa
--- /dev/null
+++ b/demos/next-auth/pages/policy.tsx
@@ -0,0 +1,32 @@
+import Layout from "../components/layout"
+
+export default function PolicyPage() {
+ return (
+
+
+ This is an example site to demonstrate how to use{" "}
+ NextAuth.js for authentication.
+
+ Terms of Service
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ Privacy Policy
+
+ This site uses JSON Web Tokens and an in-memory database which resets
+ every ~2 hours.
+
+
+ Data provided to this site is exclusively used to support signing in and
+ is not passed to any third party services, other than via SMTP or OAuth
+ for the purposes of authentication.
+
+
+ )
+}
diff --git a/demos/next-auth/pages/protected.tsx b/demos/next-auth/pages/protected.tsx
new file mode 100644
index 0000000000..8b6d2d8704
--- /dev/null
+++ b/demos/next-auth/pages/protected.tsx
@@ -0,0 +1,44 @@
+import { useState, useEffect } from "react"
+import { useSession } from "next-auth/react"
+import Layout from "../components/layout"
+import AccessDenied from "../components/access-denied"
+
+export default function ProtectedPage() {
+ const { data: session, status } = useSession()
+ const loading = status === "loading"
+ const [content, setContent] = useState()
+
+ // Fetch content from protected route
+ useEffect(() => {
+ const fetchData = async () => {
+ const res = await fetch("/api/examples/protected")
+ const json = await res.json()
+ if (json.content) {
+ setContent(json.content)
+ }
+ }
+ fetchData()
+ }, [session])
+
+ // When rendering client side don't display anything until loading is complete
+ if (typeof window !== "undefined" && loading) return null
+
+ // If no session exists, display access denied message
+ if (!session) {
+ return (
+
+
+
+ )
+ }
+
+ // If session exists, display content
+ return (
+
+ Protected Page
+
+ {content ?? "\u00a0"}
+
+
+ )
+}
diff --git a/demos/next-auth/pages/server.tsx b/demos/next-auth/pages/server.tsx
new file mode 100644
index 0000000000..fd5c710cfc
--- /dev/null
+++ b/demos/next-auth/pages/server.tsx
@@ -0,0 +1,44 @@
+import { useSession, getSession } from "next-auth/react"
+import Layout from "../components/layout"
+import type { NextPageContext } from "next"
+
+export default function ServerSidePage() {
+ // As this page uses Server Side Rendering, the `session` will be already
+ // populated on render without needing to go through a loading stage.
+ // This is possible because of the shared context configured in `_app.js` that
+ // is used by `useSession()`.
+ const { data: session, status } = useSession()
+ const loading = status === "loading"
+
+ return (
+
+ Server Side Rendering
+
+ This page uses the universal getSession() method in{" "}
+ getServerSideProps().
+
+
+ Using getSession() in{" "}
+ getServerSideProps() is the recommended approach if you
+ need to support Server Side Rendering with authentication.
+
+
+ The advantage of Server Side Rendering is this page does not require
+ client side JavaScript.
+
+
+ The disadvantage of Server Side Rendering is that this page is slower to
+ render.
+
+
+ )
+}
+
+// Export the `session` prop to use sessions with Server Side Rendering
+export async function getServerSideProps(context: NextPageContext) {
+ return {
+ props: {
+ session: await getSession(context),
+ },
+ }
+}
diff --git a/demos/next-auth/pages/styles.css b/demos/next-auth/pages/styles.css
new file mode 100644
index 0000000000..0b3f9ee3df
--- /dev/null
+++ b/demos/next-auth/pages/styles.css
@@ -0,0 +1,32 @@
+body {
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
+ "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
+ "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ padding: 0 1rem 1rem 1rem;
+ max-width: 680px;
+ margin: 0 auto;
+ background: #fff;
+ color: #333;
+}
+
+li,
+p {
+ line-height: 1.5rem;
+}
+
+a {
+ font-weight: 500;
+}
+
+hr {
+ border: 1px solid #ddd;
+}
+
+iframe {
+ background: #ccc;
+ border: 1px solid #ccc;
+ height: 10rem;
+ width: 100%;
+ border-radius: 0.5rem;
+ filter: invert(1);
+}
diff --git a/demos/next-auth/process.d.ts b/demos/next-auth/process.d.ts
new file mode 100644
index 0000000000..b140ecb408
--- /dev/null
+++ b/demos/next-auth/process.d.ts
@@ -0,0 +1,16 @@
+declare namespace NodeJS {
+ export interface ProcessEnv {
+ NEXTAUTH_URL: string
+ NEXTAUTH_SECRET: string
+ GITHUB_ID: string
+ GITHUB_SECRET: string
+ FACEBOOK_ID: string
+ FACEBOOK_SECRET: string
+ TWITTER_ID: string
+ TWITTER_SECRET: string
+ GOOGLE_ID: string
+ GOOGLE_SECRET: string
+ AUTH0_ID: string
+ AUTH0_SECRET: string
+ }
+}
diff --git a/demos/next-auth/tsconfig.json b/demos/next-auth/tsconfig.json
new file mode 100644
index 0000000000..3ccf087f5a
--- /dev/null
+++ b/demos/next-auth/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true
+ },
+ "include": ["process.d.ts", "next-env.d.ts", "next-auth.d.ts", "**/*.ts", "**/*.tsx"],
+ "exclude": ["node_modules"]
+}
\ No newline at end of file
diff --git a/plugin/src/helpers/config.ts b/plugin/src/helpers/config.ts
index 3142e33715..0ef80cdc8d 100644
--- a/plugin/src/helpers/config.ts
+++ b/plugin/src/helpers/config.ts
@@ -1,4 +1,4 @@
-import { readJSON } from 'fs-extra'
+import { readJSON, writeJSON } from 'fs-extra'
import type { NextConfigComplete } from 'next/dist/server/config-shared'
import { join, dirname, relative } from 'pathe'
import slash from 'slash'
@@ -36,6 +36,24 @@ export const getNextConfig = async function getNextConfig({
}
}
+/**
+ * Returns all of the NextJS configuration stored within 'required-server-files.json'
+ * To update the configuration within this file, use the 'updateRequiredServerFiles' method.
+ */
+export const getRequiredServerFiles = async (publish: string): Promise => {
+ const configFile = join(publish, 'required-server-files.json')
+ return await readJSON(configFile)
+}
+
+/**
+ * Writes a modified configuration object to 'required-server-files.json'.
+ * To get the full configuration, use the 'getRequiredServerFiles' method.
+ */
+export const updateRequiredServerFiles = async (publish: string, modifiedConfig: RequiredServerFiles) => {
+ const configFile = join(publish, 'required-server-files.json')
+ await writeJSON(configFile, modifiedConfig)
+}
+
const resolveModuleRoot = (moduleName) => {
try {
return dirname(relative(process.cwd(), require.resolve(`${moduleName}/package.json`, { paths: [process.cwd()] })))
diff --git a/plugin/src/helpers/utils.ts b/plugin/src/helpers/utils.ts
index e43c033a58..95470e93b1 100644
--- a/plugin/src/helpers/utils.ts
+++ b/plugin/src/helpers/utils.ts
@@ -174,3 +174,14 @@ export const findModuleFromBase = ({ paths, candidates }): string | null => {
}
return null
}
+
+export const isNextAuthInstalled = (): boolean => {
+ try {
+ // eslint-disable-next-line import/no-unassigned-import, import/no-unresolved, node/no-missing-require
+ require('next-auth')
+ return true
+ } catch {
+ // Ignore the MODULE_NOT_FOUND error
+ return false
+ }
+}
diff --git a/plugin/src/index.ts b/plugin/src/index.ts
index eeae14d18b..87b1d4bd4a 100644
--- a/plugin/src/index.ts
+++ b/plugin/src/index.ts
@@ -7,12 +7,17 @@ import { outdent } from 'outdent'
import { HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME } from './constants'
import { restoreCache, saveCache } from './helpers/cache'
-import { getNextConfig, configureHandlerFunctions } from './helpers/config'
+import {
+ getNextConfig,
+ getRequiredServerFiles,
+ updateRequiredServerFiles,
+ configureHandlerFunctions,
+} from './helpers/config'
import { updateConfig, writeMiddleware } from './helpers/edge'
import { moveStaticPages, movePublicFiles, patchNextFiles, unpatchNextFiles } from './helpers/files'
import { generateFunctions, setupImageFunction, generatePagesResolver } from './helpers/functions'
import { generateRedirects, generateStaticRedirects } from './helpers/redirects'
-import { shouldSkip } from './helpers/utils'
+import { shouldSkip, isNextAuthInstalled } from './helpers/utils'
import {
verifyNetlifyBuildVersion,
checkNextSiteHasBuilt,
@@ -70,6 +75,15 @@ const plugin: NetlifyPlugin = {
failBuild,
})
+ if (isNextAuthInstalled()) {
+ console.log(`NextAuth package detected, setting NEXTAUTH_URL environment variable to ${process.env.URL}`)
+
+ const config = await getRequiredServerFiles(publish)
+ config.config.env.NEXTAUTH_URL = process.env.URL
+
+ await updateRequiredServerFiles(publish, config)
+ }
+
const buildId = readFileSync(join(publish, 'BUILD_ID'), 'utf8').trim()
configureHandlerFunctions({ netlifyConfig, ignore, publish: relative(process.cwd(), publish) })
diff --git a/test/index.js b/test/index.js
index 532df96902..19aeb8ec65 100644
--- a/test/index.js
+++ b/test/index.js
@@ -1,3 +1,10 @@
+jest.mock('../plugin/src/helpers/utils', () => {
+ return {
+ ...(jest.requireActual('../plugin/src/helpers/utils')),
+ isNextAuthInstalled: jest.fn()
+ }
+});
+
const { writeJSON, unlink, existsSync, readFileSync, copy, ensureDir, readJson } = require('fs-extra')
const path = require('path')
const process = require('process')
@@ -18,6 +25,7 @@ const {
patchNextFiles,
unpatchNextFiles,
} = require('../plugin/src/helpers/files')
+const { getRequiredServerFiles } = require('../plugin/src/helpers/config');
const { dirname } = require('path')
const { getProblematicUserRewrites } = require('../plugin/src/helpers/verification')
@@ -205,6 +213,48 @@ describe('preBuild()', () => {
})
describe('onBuild()', () => {
+ const { isNextAuthInstalled } = require('../plugin/src/helpers/utils')
+
+ beforeEach(() => {
+ isNextAuthInstalled.mockImplementation(() => {
+ return true;
+ })
+ })
+
+ test('sets NEXTAUTH_URL when next-auth package is detected', async () => {
+ const mockSiteUrl = "https://my-netlify-site.app";
+
+ // Value represents the main address to the site and is either
+ // a Netlify subdomain or custom domain set by the user.
+ // See https://docs.netlify.com/configure-builds/environment-variables/#deploy-urls-and-metadata
+ process.env.URL = mockSiteUrl;
+
+ await moveNextDist()
+
+ await plugin.onBuild(defaultArgs)
+
+ expect(onBuildHasRun(netlifyConfig)).toBe(true)
+ const config = await getRequiredServerFiles(netlifyConfig.build.publish);
+
+ expect(config.config.env.NEXTAUTH_URL).toEqual(mockSiteUrl)
+
+ delete process.env.URL;
+ })
+
+ test('skips setting NEXTAUTH_URL when next-auth package is not found', async () => {
+ isNextAuthInstalled.mockImplementation(() => {
+ return false;
+ })
+
+ await moveNextDist()
+ await plugin.onBuild(defaultArgs)
+
+ expect(onBuildHasRun(netlifyConfig)).toBe(true)
+ const config = await getRequiredServerFiles(netlifyConfig.build.publish);
+
+ expect(config.config.env.NEXTAUTH_URL).toBeUndefined();
+ })
+
test('runs onBuild', async () => {
await moveNextDist()