Compare commits

..

48 Commits

Author SHA1 Message Date
Balázs Orbán
f2a07932b9 chore(release): bump package version(s) [skip ci] 2022-12-03 12:38:32 +01:00
Balázs Orbán
25c7ce1d2b chore: revert sync-example action 2022-12-03 12:31:22 +01:00
Vedant
227a233bd8 chore(adapters): update firebase sdk version in firebase adapter (#5902)
* Update firebase SDK version

* Update pnpm-lock.yaml

* add `--debug`

* move flag

* add `FIREBASE_TOKEN`

* chore: upgrade `firebase-tools`

* revert peer dep change

* remove --debug flag

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2022-12-03 11:29:09 +00:00
Vedant
cf9f133aa3 fix(adapters): tsconfig for firebase and pouchdb (#5945)
* Update tsconfig.json

* Update tsconfig.json
2022-12-03 09:48:55 +01:00
Arnaud Zheng
2301c1be44 chore(types): fix typo in comment (#5815)
Co-authored-by: Nico Domino <yo@ndo.dev>
2022-12-02 16:39:28 +01:00
jintak0401
6e408e24bf fix(provider): modify response.name to response.nickname (Naver) (#5915)
fix(provider): modify response.name to response.nickname (Naver Provider)

Co-authored-by: Thang Vu <hi@thvu.dev>
2022-12-01 08:12:16 +07:00
Jason Brady
f277989c69 feat(core): make pkce and state maxAge configurable on the cookies (#4719)
* feat(cookies): make pkce and state maxAge configurable on the cookies (#4660)

* added tests for pkce and state handlers
2022-12-01 08:02:42 +07:00
Nico Domino
6146e93288 chore(docs): add new provider styling notes to CONTRIBUTING.md (#5900) 2022-11-30 15:44:50 +01:00
Nico Domino
1ff565da6c chore(docs): update oauth.md styling docs (#5899)
* chore(docs): update oauth.md styling docs

* chore: add note about styling object

* chore: typo

* chore: filename typos
2022-11-30 15:43:55 +01:00
Nico Domino
41f75cf870 chore(actions): version bump all deps (#5903) 2022-11-28 11:10:15 +00:00
Justin W Hall
dd591ed8d0 chore(docs): fix path Strava provider file (#5853)
Co-authored-by: Nico Domino <yo@ndo.dev>
2022-11-27 14:04:17 +01:00
koolskateguy89
297bc2317f chore(docs): fix spelling in docs (#5867)
Co-authored-by: Nico Domino <yo@ndo.dev>
2022-11-27 13:44:35 +01:00
Tormod Flesjø
b170138e70 chore: update mongodb.md with typescript filetypes (#5889) 2022-11-27 13:11:54 +01:00
Balázs Orbán
a307079e0f fix(ts): improve unstable_getServerSession return type (#5792)
Co-authored-by: Thang Vu <hi@thvu.dev>
2022-11-24 15:13:05 +01:00
Jacob Penny
d52b7a6b7d chore(adapters): fix typo on Firebase Adapter (#5813) 2022-11-24 14:31:43 +01:00
Jake Mulley
30b69a07eb chore: fix import statement for next-auth/providers/email (#5860) 2022-11-21 12:07:09 +01:00
Balázs Orbán
0d1757814f fix(next): improve dev environment variable handling (#5763)
* fix(next): HIDE `NEXTAUTH_URL` warning locally

* refactor: move out `process.env` from core

* fix tests

* simplify

* swap
2022-11-20 09:08:10 +00:00
Thang Vu
068f9b50b8 chore(release): bump package version(s) [skip ci] 2022-11-19 21:29:28 +07:00
Martin Sonnberger
dac490b7a1 feat(adapters): Add Supabase adapter (#5050)
* Add Supabase adapter

* Add Supabase adapter

* Add schema setup to docs

* supabase config changes

* chore: update to supabase-js v2.

* chore: migrate to next_auth schema.

* feat: add supabase examples.

* chore: update docs.

* chore: add telemetry.

* fix: resolve issues after merge.

* chore: extend session type.

* typo

* chore: remove unnecessary grants.

* fix: schema constraints.

Co-authored-by: thorwebdev <thor@supabase.io>
Co-authored-by: Thor 雷神 Schaeff <5748289+thorwebdev@users.noreply.github.com>
Co-authored-by: Thang Vu <hi@thvu.dev>
2022-11-19 20:56:20 +07:00
Gregor Adams
c0f51669e2 docs: upadte mongodb guide (#5809)
Co-authored-by: Nico Domino <yo@ndo.dev>
2022-11-16 16:38:55 +01:00
Nico Domino
cbf8ce3510 chore: fix signin btns svg URLs to 'main' (#5826) 2022-11-15 22:07:30 +01:00
Nico Domino
1c19cc71df chore: add tour de source link to CONTRIBUTING.md (#5820) 2022-11-15 16:47:15 +00:00
Nico Domino
9d962a0056 feat: add signin button styles (#5802)
* feat: add signin button styles

* fix: remove unnecessary spans

chore: rm comments

* Update packages/next-auth/src/core/pages/signin.tsx

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* feat: add provider svgs to repo

* fix: adjust SVG sizes

* fix: adjust provider btn to logo links

* feat: apple provider btn style

* fix: add apple-dark svg

* feat: atlassian logo and style

* feat: auth0 logo and style

* feat: azure logo and style

* fix: azure logo size

* feat: battlenet logo and style

* feat: box logo and style

* feat: cognito logo and style

* feat: discord logo and style

* feat: facebook logo and style

* feat: foursquare logo and style

* fix: foursquare logo size

* feat: freshbooks logo and style

* feat: gitlab logo and style

* fix: gitlab logo whitespace

* feat: hubspot logo and style

* feat: instagram logo and style

* feat: keycloak logo and style

* feat: keycloak logo resize

* feat: linkedin logo and style

* feat: mailchimp logo and style

* feat: okta logo and style

* feat: update okta logos

* feat: patreon logo and style

* fix: okta logo viewbox

* feat: slack logo and style

* feat: spotify logo and style

* feat: todoist logo and style

* fix: spotify logo size

* feat: trakt logo and style

* feat: twitch logo and style

* feat: twitter logo and style

* feat: vk logo and style

* feat: wikimedia logo and style

* feat: workos logo and style

* fix: wikimedia-dark logo + twitter (legacy)

* fix: button:active styling

* fix: ignore eslint inline css custom properties warning

* fix: improve darkmode default btn stylign

* fix: swap github btn colors

* fix: swap line btn theme colors

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2022-11-15 16:46:42 +00:00
Håkon Collett Bjørgan
8387c78e3f fix(core): update JSDoc for jwt in NextAuthOptions (#5804) 2022-11-13 07:55:35 +01:00
Balázs Orbán
58e30a6af6 chore(release): bump package version(s) [skip ci] 2022-11-07 21:33:48 +01:00
Balázs Orbán
b7ff987baf docs: update unstable_getServerSession in RSC 2022-11-07 21:20:06 +01:00
Balázs Orbán
782812a52b fix(next): correctly parse headers with RSC (#5753)
* fix(next): correctly parse headers with RSC

* chore(dev): simplify/fix dev app

* make authOptions optional for RSC case
2022-11-07 20:19:14 +00:00
Balázs Orbán
32f2a0cea3 chore(release): bump package version(s) [skip ci] 2022-11-07 03:40:46 +01:00
Balázs Orbán
3343ef18b2 fix(next): conditional RSC support (#5745)
* fix(next): conditional RSC

* use Node 16 for version determination

* convert cookies

* use map instead of reduce
2022-11-07 02:33:47 +00:00
Robert Soriano
6280fe9e10 docs: add Nuxt 3 playground (#5746) 2022-11-06 20:33:02 +01:00
Balázs Orbán
52a2bf3e28 chore(release): bump package version(s) [skip ci] 2022-11-06 06:24:30 +01:00
Balázs Orbán
180c6252d9 fix(next): build RSC+unstable_getServerSession 2022-11-06 06:09:27 +01:00
Balázs Orbán
362e981e6d chore(release): bump package version(s) [skip ci] 2022-11-06 06:07:47 +01:00
Balázs Orbán
5198eb19f7 fix(next): build RSC+unstable_getServerSession 2022-11-06 05:53:38 +01:00
Balázs Orbán
0210cfccf3 chore(release): bump package version(s) [skip ci] 2022-11-06 05:12:31 +01:00
Balázs Orbán
e90925bea0 feat(next): allow unstable_getServerSession in Server Components (#5741)
* feat(next): support Server Components with `unstable_getServerSession`

* chore: remove `.entries`

* docs(next): add documentation for RSC

* update beta docs

* chore(dev): add app dir

* fix text

* only show second warning if using with RSC

* only delete expires for RSC case
2022-11-06 04:03:26 +00:00
Leif Arriens
27a0b70d87 docs: fix import path at providers/oauth (#5725) (#5736) 2022-11-06 00:54:01 +01:00
Balázs Orbán
c676e93d8a chore(release): bump package version(s) [skip ci] 2022-11-05 23:43:00 +01:00
Matthew Francis Brunetti
f498e9cd0a fix(react): allow imports from "next-auth/react" in RSC (#5718) 2022-11-05 23:09:51 +01:00
Balázs Orbán
2f3396d376 chore(release): bump package version(s) [skip ci] 2022-11-04 03:48:42 +01:00
Balázs Orbán
e62f879ebd chore(adapters): run Xata tests 2022-11-04 03:38:24 +01:00
Balázs Orbán
f67959eb04 chore: update example to Next.js 13 2022-11-04 03:33:42 +01:00
Henrik Wenz
060953dacf fix: support Next.js 13 (#5710)
* Migrate dev app to Next.js Version 13

* Update core types

* Fix middleware

https://github.com/nextauthjs/next-auth/issues/5649

* Use new ResponseCookie API

https://github.com/vercel/next.js/pull/41526
2022-11-04 03:32:48 +01:00
Nico Miebach
30ad639d16 fix: Add support for Node 18 (#5656)
* add support for Node 18

* update node engines in packages/next-auth

Co-authored-by: Thang Vu <hi@thvu.dev>
2022-10-30 23:54:16 -07:00
Thang Vu
777da4302d fix: add next 13 as peer dependencies (#5657)
fix: add next 13 as peer dependencies
2022-10-30 23:46:24 -07:00
Tejas Kumar
733fd5f234 feat(adapters): add Xata adapter (#4911)
* Add Xata adapter

* Adjust slogan

* Namespace tables for better DX

* Fix types

* Adjust docs

* Import type only

* Add scripts

* Fix types

* Update packages/adapter-xata/package.json

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* Update packages/adapter-xata/package.json

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* Update packages/adapter-xata/src/index.ts

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* Update packages/adapter-xata/src/index.ts

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* Update packages/adapter-xata/src/index.ts

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* Update docs/docs/adapters/xata.md

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* Update docs/docs/adapters/xata.md

Co-authored-by: Balázs Orbán <info@balazsorban.com>

* Address more comments

* Clarify codegen

* Fix by adding src

* Fix types

* More cleanup

* Fix import

* Clean up model

* Adjust docs

* Update docs

* Housekeeping

* Add tests

* Update lockfile

* Update packages/adapter-xata/tsconfig.json

Co-authored-by: Thang Vu <hi@thvu.dev>

* Update packages/adapter-xata/src/index.ts

Co-authored-by: Balázs Orbán <info@balazsorban.com>
Co-authored-by: Thang Vu <hi@thvu.dev>
2022-10-27 21:39:49 +07:00
Thang Vu
a787efc6be docs: improve documentation for adding new provider (#5633) 2022-10-25 10:05:52 +07:00
Bruno Crosier
261968b9bb docs: improve credentials docs (#5627) 2022-10-24 23:33:46 +07:00
209 changed files with 14432 additions and 2452 deletions

View File

@@ -31,8 +31,10 @@ body:
- "@next-auth/pouchdb-adapter"
- "@next-auth/prisma-adapter"
- "@next-auth/sequelize-adapter"
- "@next-auth/supabase-adapter"
- "@next-auth/typeorm-legacy-adapter"
- "@next-auth/upstash-redis-adapter"
- "@next-auth/xata-adapter"
validations:
required: true
- type: textarea

View File

@@ -30,8 +30,14 @@ prisma:
sequelize:
- "@next-auth/sequelize-adapter"
supabase:
- "@next-auth/supabase-adapter"
typeorm-legacy:
- "@next-auth/typeorm-legacy-adapter"
upstash-redis:
- "@next-auth/upstash-redis-adapter"
xata:
- "@next-auth/xata-adapter"

View File

@@ -42,12 +42,18 @@ prisma:
sequelize:
- packages/adapter-sequelize/**
supabase:
- packages/adapter-supabase/**
typeorm-legacy:
- packages/adapter-typeorm-legacy/**
upstash-redis:
- packages/adapter-upstash-redis/**
xata:
- packages/adapter-xata/**
core:
- packages/next-auth/src/**/*

View File

@@ -18,10 +18,10 @@ jobs:
language: ["javascript"]
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

View File

@@ -11,7 +11,7 @@ jobs:
name: Triage
runs-on: ubuntu-latest
steps:
- uses: github/issue-labeler@v2.4.1
- uses: github/issue-labeler@v2.5
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
configuration-path: ".github/issue-labeler.yml"

View File

@@ -10,7 +10,7 @@ jobs:
name: Triage
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v3
- uses: actions/labeler@v4
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
configuration-path: ".github/pr-labeler.yml"

View File

@@ -15,17 +15,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Init
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 2
- name: Install pnpm
uses: pnpm/action-setup@v2.2.1
uses: pnpm/action-setup@v2.2.4
with:
version: 7.5.1
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16
node-version: 18
cache: "pnpm"
- name: Install dependencies
run: pnpm install
@@ -49,17 +49,17 @@ jobs:
environment: Production
steps:
- name: Init
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install pnpm
uses: pnpm/action-setup@v2.2.1
uses: pnpm/action-setup@v2.2.4
with:
version: 7.5.1
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16
node-version: 18
cache: "pnpm"
- name: Install dependencies
run: pnpm install
@@ -81,15 +81,15 @@ jobs:
environment: Preview
steps:
- name: Init
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Install pnpm
uses: pnpm/action-setup@v2.2.1
uses: pnpm/action-setup@v2.2.4
with:
version: 7.5.1
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 16
node-version: 18
cache: "pnpm"
- name: Install dependencies
run: pnpm install
@@ -106,7 +106,7 @@ jobs:
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN_PKG }}
- name: Comment version on PR
uses: NejcZdovc/comment-pr@v1
uses: NejcZdovc/comment-pr@v2
with:
message:
"🎉 Experimental release [published 📦️ on npm](https://npmjs.com/package/next-auth/v/${{ env.VERSION }})!\n \

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Run GitHub File Sync
# Can update to v1 when https://github.com/BetaHuhn/repo-file-sync-action/issues/168 is resolved
uses: BetaHuhn/repo-file-sync-action@v1.16.5

1
.gitignore vendored
View File

@@ -65,6 +65,7 @@ dev.db*
packages/adapter-prisma/prisma/dev.db
packages/adapter-prisma/prisma/migrations
db.sqlite
packages/adapter-supabase/supabase/.branches
# Tests
coverage

2
.nvmrc
View File

@@ -1 +1 @@
16
18

View File

@@ -12,6 +12,8 @@ Please raise any significant new functionality or breaking change an issue for d
Anyone can be a contributor. Either you found a typo, or you have an awesome feature request you could implement, we encourage you to create a Pull Request.
Before contributing, we recommend you read the [Tour de Source: NextAuth.js](https://sourcegraph.com/notebooks/Tm90ZWJvb2s6MTc2MQ==) post to become more familiar with the libraries inner workings.
### Pull Requests
- The latest changes are always in `main`, so please make your Pull Request against that branch.
@@ -41,7 +43,7 @@ corepack enable pnpm
(Now, if you run `pnpm --version`, it should print the same verion as the `packageManager` property in the [`package.json` file](https://github.com/nextauthjs/next-auth/blob/main/package.json))
3. Install packages. Developing requires Node.js v16:
3. Install packages. Developing requires Node.js v18:
```sh
pnpm install
@@ -85,6 +87,7 @@ If you think your custom provider might be useful to others, we encourage you to
1. Add your config: [`src/providers/{provider}.js`](https://github.com/nextauthjs/next-auth/tree/main/packages/next-auth/src/providers) (Make sure you use a named default export, like `export default function YourProvider`!)
2. Add provider documentation: [`www/docs/providers/{provider}.md`](https://github.com/nextauthjs/next-auth/tree/main/www/docs/providers)
3. Add provider logo svgs, like `google-dark.svg` (dark mode) and `google.svg` (light mode) to the `/packages/next-auth/provider-logos/` directory. Don't forget to set the provider's styling options in the `provider.style` config object.
That's it! 🎉 Others will be able to discover this provider much more easily now!
@@ -116,4 +119,4 @@ When accepting Pull Requests, make sure the following:
### Skipping a release
If a commit contains `[skip release]` in their message, it will be excluded from the commit analysis and won't participate in the release type determination. This is useful, if the PR being merged should not trigger a new `npm` release.
If a commit contains `[skip release]` in their message, it will be excluded from the commit analysis and won't participate in the release type determination. This is useful, if the PR being merged should not trigger a new `npm` release.

View File

@@ -48,4 +48,11 @@ EMAIL_FROM=user@gmail.com
DATABASE_URL=
WIKIMEDIA_ID=
WIKIMEDIA_SECRET=
WIKIMEDIA_SECRET=
# Supabase Example Configuration
# Supabase Example Configuration
# NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321
# SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSJ9.vI9obAHOGyVVKa3pD--kJlyxp-Z2zV9UUMAhKpNLAcU
# SUPABASE_JWT_SECRET=super-secret-jwt-token-with-at-least-32-characters-long
# NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs

12
apps/dev/app/layout.tsx Normal file
View File

@@ -0,0 +1,12 @@
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<head></head>
<body>{children}</body>
</html>
)
}

View File

@@ -0,0 +1,6 @@
import { unstable_getServerSession } from "next-auth/next"
export default async function Page() {
const session = await unstable_getServerSession()
return <pre>{JSON.stringify(session, null, 2)}</pre>
}

View File

@@ -17,9 +17,7 @@ export default function Footer() {
<a href="https://github.com/nextauthjs/next-auth-example">GitHub</a>
</li>
<li className={styles.navItem}>
<Link href="/policy">
<a>Policy</a>
</Link>
<Link href="/policy">Policy</Link>
</li>
<li className={styles.navItem}>
<em>{packageJSON.version}</em>

View File

@@ -64,49 +64,37 @@ export default function Header() {
<nav>
<ul className={styles.navItems}>
<li className={styles.navItem}>
<Link href="/">
<a>Home</a>
</Link>
<Link href="/">Home</Link>
</li>
<li className={styles.navItem}>
<Link href="/client">
<a>Client</a>
</Link>
<Link href="/client">Client</Link>
</li>
<li className={styles.navItem}>
<Link href="/server">
<a>Server</a>
</Link>
<Link href="/server">Server</Link>
</li>
<li className={styles.navItem}>
<Link href="/protected">
<a>Protected</a>
</Link>
<Link href="/protected">Protected</Link>
</li>
<li className={styles.navItem}>
<Link href="/protected-ssr">
<a>Protected(SSR)</a>
</Link>
<Link href="/protected-ssr">Protected(SSR)</Link>
</li>
<li className={styles.navItem}>
<Link href="/api-example">
<a>API</a>
</Link>
<Link href="/api-example">API</Link>
</li>
<li className={styles.navItem}>
<Link href="/credentials">
<a>Credentials</a>
</Link>
<Link href="/credentials">Credentials</Link>
</li>
<li className={styles.navItem}>
<Link href="/email">
<a>Email</a>
</Link>
<Link href="/email">Email</Link>
</li>
<li className={styles.navItem}>
<Link href="/middleware-protected">
<a>Middleware protected</a>
</Link>
<Link href="/middleware-protected">Middleware protected</Link>
</li>
<li className={styles.navItem}>
<Link href="/supabase-client-rls">Supabase RLS</Link>
</li>
<li className={styles.navItem}>
<Link href="/supabase-ssr">Supabase RLS(SSR)</Link>
</li>
</ul>
</nav>

View File

@@ -4,5 +4,6 @@ module.exports = {
config.experiments = { ...config.experiments, topLevelAwait: true }
return config
},
experimental: { appDir: true },
typescript: { ignoreBuildErrors: true },
}

View File

@@ -16,16 +16,20 @@
"dependencies": {
"@next-auth/fauna-adapter": "workspace:*",
"@next-auth/prisma-adapter": "workspace:*",
"@next-auth/supabase-adapter": "workspace:*",
"@next-auth/typeorm-legacy-adapter": "workspace:*",
"@prisma/client": "^3",
"@supabase/supabase-js": "^2.0.5",
"faunadb": "^4",
"next": "12.3.1",
"jsonwebtoken": "^8.5.1",
"next": "13.0.2",
"next-auth": "workspace:*",
"nodemailer": "^6",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@types/jsonwebtoken": "^8.5.5",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"fake-smtp-server": "^0.8.0",

View File

@@ -1,5 +1,6 @@
import NextAuth from "next-auth"
import type { NextAuthOptions } from "next-auth"
import jwt from "jsonwebtoken"
// Providers
import Apple from "next-auth/providers/apple"
@@ -18,7 +19,6 @@ import Freshbooks from "next-auth/providers/freshbooks"
import GitHub from "next-auth/providers/github"
import Gitlab from "next-auth/providers/gitlab"
import Google from "next-auth/providers/google"
import Hubspot from "next-auth/providers/hubspot"
import IDS4 from "next-auth/providers/identity-server4"
import Instagram from "next-auth/providers/instagram"
import Keycloak from "next-auth/providers/keycloak"
@@ -30,43 +30,76 @@ import Osu from "next-auth/providers/osu"
import Patreon from "next-auth/providers/patreon"
import Slack from "next-auth/providers/slack"
import Spotify from "next-auth/providers/spotify"
import Todoist from "next-auth/providers/todoist"
import Trakt from "next-auth/providers/trakt"
import Twitch from "next-auth/providers/twitch"
import Twitter, { TwitterLegacy } from "next-auth/providers/twitter"
import Vk from "next-auth/providers/vk"
import Wikimedia from "next-auth/providers/wikimedia"
import WorkOS from "next-auth/providers/workos"
import Zitadel from "next-auth/providers/zitadel"
// Adapters
import { PrismaClient } from "@prisma/client"
import { PrismaAdapter } from "@next-auth/prisma-adapter"
import { Client as FaunaClient } from "faunadb"
import { FaunaAdapter } from "@next-auth/fauna-adapter"
import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter"
import { SupabaseAdapter } from "@next-auth/supabase-adapter"
// // Prisma
// import { PrismaClient } from "@prisma/client"
// import { PrismaAdapter } from "@next-auth/prisma-adapter"
// const client = globalThis.prisma || new PrismaClient()
// if (process.env.NODE_ENV !== "production") globalThis.prisma = client
// const adapter = PrismaAdapter(client)
// // Fauna
// import { Client as FaunaClient } from "faunadb"
// import { FaunaAdapter } from "@next-auth/fauna-adapter"
// const opts = { secret: process.env.FAUNA_SECRET, domain: process.env.FAUNA_DOMAIN }
// const client = globalThis.fauna || new FaunaClient(opts)
// if (process.env.NODE_ENV !== "production") globalThis.fauna = client
// const adapter = FaunaAdapter(client)
// // TypeORM
// import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter"
// const adapter = TypeORMLegacyAdapter({
// type: "sqlite",
// name: "next-auth-test-memory",
// database: "./typeorm/dev.db",
// synchronize: true,
// })
// Add an adapter you want to test here.
const adapters = {
prisma() {
const client = globalThis.prisma || new PrismaClient()
if (process.env.NODE_ENV !== "production") globalThis.prisma = client
return PrismaAdapter(client)
},
typeorm() {
return TypeORMLegacyAdapter({
type: "sqlite",
name: "next-auth-test-memory",
database: "./typeorm/dev.db",
synchronize: true,
})
},
fauna() {
const client =
globalThis.fauna ||
new FaunaClient({
secret: process.env.FAUNA_SECRET,
domain: process.env.FAUNA_DOMAIN,
})
if (process.env.NODE_ENV !== "production") global.fauna = client
return FaunaAdapter(client)
},
supabase() {
return SupabaseAdapter({
url: process.env.NEXT_PUBLIC_SUPABASE_URL,
secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
})
},
noop() {
return undefined
},
}
export const authOptions: NextAuthOptions = {
// adapter,
adapter: adapters.noop(),
callbacks: {
async session({ session, user }) {
// NOTE: this is needed when using Supabase with RLS. Otherwise this callback can be removed.
const signingSecret = process.env.SUPABASE_JWT_SECRET
if (signingSecret) {
const payload = {
aud: "authenticated",
exp: Math.floor(new Date(session.expires).getTime() / 1000),
sub: user.id,
email: user.email,
role: "authenticated",
}
session.supabaseAccessToken = jwt.sign(payload, signingSecret)
}
return session
},
},
debug: process.env.NODE_ENV !== "production",
theme: {
logo: "https://next-auth.js.org/img/logo/logo-sm.png",
@@ -94,7 +127,6 @@ export const authOptions: NextAuthOptions = {
GitHub({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET }),
Gitlab({ clientId: process.env.GITLAB_ID, clientSecret: process.env.GITLAB_SECRET }),
Google({ clientId: process.env.GOOGLE_ID, clientSecret: process.env.GOOGLE_SECRET }),
Hubspot({ clientId: process.env.HUBSPOT_ID, clientSecret: process.env.HUBSPOT_SECRET }),
IDS4({ clientId: process.env.IDS4_ID, clientSecret: process.env.IDS4_SECRET, issuer: process.env.IDS4_ISSUER }),
Instagram({ clientId: process.env.INSTAGRAM_ID, clientSecret: process.env.INSTAGRAM_SECRET }),
Keycloak({ clientId: process.env.KEYCLOAK_ID, clientSecret: process.env.KEYCLOAK_SECRET, issuer: process.env.KEYCLOAK_ISSUER }),
@@ -106,7 +138,6 @@ export const authOptions: NextAuthOptions = {
Patreon({ clientId: process.env.PATREON_ID, clientSecret: process.env.PATREON_SECRET }),
Slack({ clientId: process.env.SLACK_ID, clientSecret: process.env.SLACK_SECRET }),
Spotify({ clientId: process.env.SPOTIFY_ID, clientSecret: process.env.SPOTIFY_SECRET }),
Todoist({ clientId: process.env.TODOIST_ID, clientSecret: process.env.TODOIST_SECRET }),
Trakt({ clientId: process.env.TRAKT_ID, clientSecret: process.env.TRAKT_SECRET }),
Twitch({ clientId: process.env.TWITCH_ID, clientSecret: process.env.TWITCH_SECRET }),
Twitter({ version: "2.0", clientId: process.env.TWITTER_ID, clientSecret: process.env.TWITTER_SECRET }),
@@ -114,7 +145,6 @@ export const authOptions: NextAuthOptions = {
Vk({ clientId: process.env.VK_ID, clientSecret: process.env.VK_SECRET }),
Wikimedia({ clientId: process.env.WIKIMEDIA_ID, clientSecret: process.env.WIKIMEDIA_SECRET }),
WorkOS({ clientId: process.env.WORKOS_ID, clientSecret: process.env.WORKOS_SECRET }),
Zitadel({ issuer: process.env.ZITADEL_ISSUER, clientId: process.env.ZITADEL_CLIENT_ID, clientSecret: process.env.ZITADEL_CLIENT_SECRET }),
],
}

View File

@@ -1,8 +1,8 @@
// This is an example of how to access a session from an API route
import { unstable_getServerSession } from "next-auth/next"
import { authOptions } from '../auth/[...nextauth]';
import { authOptions } from "../auth/[...nextauth]"
export default async (req, res) => {
const session = await unstable_getServerSession(req, res, authOptions)
res.send(JSON.stringify(session, null, 2))
res.json(session)
}

View File

@@ -0,0 +1,30 @@
// This is an example of how to query data from Supabase with RLS.
// Learn more about Row Levele Security (RLS): https://supabase.com/docs/guides/auth/row-level-security
import { unstable_getServerSession } from "next-auth/next"
import { authOptions } from "../auth/[...nextauth]"
import { createClient } from "@supabase/supabase-js"
export default async (req, res) => {
const session = await unstable_getServerSession(req, res, authOptions)
if (!session)
return res.send(JSON.stringify({ error: "No session!" }, null, 2))
const { supabaseAccessToken } = session
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{
global: {
headers: {
Authorization: `Bearer ${supabaseAccessToken}`,
},
},
}
)
// Now you can query with RLS enabled.
const { data, error } = await supabase.from("users").select("*")
res.send(JSON.stringify({ supabaseAccessToken, data, error }, null, 2))
}

View File

@@ -0,0 +1,49 @@
import Layout from "../components/layout"
import { useState, useEffect } from "react"
import { useSession } from "next-auth/react"
import { createClient } from "@supabase/supabase-js"
export default function Page() {
const { data: session } = useSession()
const [data, setData] = useState(null)
useEffect(() => {
if (session) {
console.log(session)
// User is logged in, let's fetch their data.
const { supabaseAccessToken } = session
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{
global: {
headers: { Authorization: `Bearer ${supabaseAccessToken}` },
},
}
)
// Fetch data with RLS enabled.
supabase
.from("users")
.select("*")
.then(({ data }) => setData(data))
}
}, [session])
return (
<Layout>
<h1>Fetch Data from Supabase with RLS</h1>
<h2>Client-side data fetching with RLS:</h2>
<pre>{JSON.stringify(data, null, 2)}</pre>
<h2>API Example</h2>
<p>
You can also use Supabase in API routes. See the code in the
`/pages/api/examples/supabase-rls.js` file.
</p>
<p>
<em>You must be signed in to see responses.</em>
</p>
<p>/api/examples/supabase-rls</p>
<iframe src="/api/examples/supabase-rls" />
</Layout>
)
}

View File

@@ -0,0 +1,68 @@
// This is an example of how to protect content using server rendering
// and fetching data from Supabase with RLS enabled.
import { unstable_getServerSession } from "next-auth/next"
import { authOptions } from "./api/auth/[...nextauth]"
import { createClient } from "@supabase/supabase-js"
import Layout from "../components/layout"
import AccessDenied from "../components/access-denied"
export default function Page({ data, session }) {
// If no session exists, display access denied message
if (!session) {
return (
<Layout>
<AccessDenied />
</Layout>
)
}
// If session exists, display content
return (
<Layout>
<h1>Protected Page</h1>
<p>Data fetched during SSR from Supabase with RSL enabled:</p>
<pre>{JSON.stringify(data, null, 2)}</pre>
</Layout>
)
}
export async function getServerSideProps(context) {
const session = await unstable_getServerSession(
context.req,
context.res,
authOptions
)
if (!session)
return {
props: {
session,
data: null,
error: "No session",
},
}
const { supabaseAccessToken } = session
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{
global: {
headers: {
Authorization: `Bearer ${supabaseAccessToken}`,
},
},
}
)
// Now you can query with RLS enabled.
const { data, error } = await supabase.from("users").select("*")
return {
props: {
session,
data,
error,
},
}
}

View File

@@ -1,7 +1,11 @@
{
"compilerOptions": {
"target": "esnext",
"lib": ["dom", "dom.iterable", "esnext"],
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
@@ -15,7 +19,20 @@
"incremental": true,
"jsx": "preserve",
"baseUrl": ".",
"plugins": [
{
"name": "next"
}
]
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules", "jest.config.js"]
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules",
"jest.config.js"
]
}

View File

@@ -6,6 +6,8 @@ declare module "next-auth" {
* Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
*/
interface Session {
// A JWT which can be used as Authorization header with supabase-js for RLS.
supabaseAccessToken?: string
user: {
/** The user's postal address. */
address: string

View File

@@ -17,9 +17,7 @@ export default function Footer() {
<a href="https://github.com/nextauthjs/next-auth-example">GitHub</a>
</li>
<li className={styles.navItem}>
<Link href="/policy">
<a>Policy</a>
</Link>
<Link href="/policy">Policy</Link>
</li>
<li className={styles.navItem}>
<em>next-auth@{packageJSON.dependencies["next-auth"]}</em>

View File

@@ -67,39 +67,25 @@ export default function Header() {
<nav>
<ul className={styles.navItems}>
<li className={styles.navItem}>
<Link href="/">
<a>Home</a>
</Link>
<Link href="/">Home</Link>
</li>
<li className={styles.navItem}>
<Link href="/client">
<a>Client</a>
</Link>
<Link href="/client">Client</Link>
</li>
<li className={styles.navItem}>
<Link href="/server">
<a>Server</a>
</Link>
<Link href="/server">Server</Link>
</li>
<li className={styles.navItem}>
<Link href="/protected">
<a>Protected</a>
</Link>
<Link href="/protected">Protected</Link>
</li>
<li className={styles.navItem}>
<Link href="/api-example">
<a>API</a>
</Link>
<Link href="/api-example">API</Link>
</li>
<li className={styles.navItem}>
<Link href="/admin">
<a>Admin</a>
</Link>
<Link href="/admin">Admin</Link>
</li>
<li className={styles.navItem}>
<Link href="/me">
<a>Me</a>
</Link>
<Link href="/me">Me</Link>
</li>
</ul>
</nav>

View File

@@ -1,12 +1,8 @@
import Header from "./header"
import Footer from "./footer"
import type { ReactChildren } from "react"
import type { ReactNode } from "react"
interface Props {
children: React.ReactNode
}
export default function Layout({ children }: Props) {
export default function Layout({ children }: { children: ReactNode }) {
return (
<>
<Header />

View File

@@ -13,13 +13,12 @@ export default function ServerSidePage({ session }: { session: Session }) {
<h1>Server Side Rendering</h1>
<p>
This page uses the <strong>unstable_getServerSession()</strong> method
in <strong>unstable_getServerSideProps()</strong>.
in <strong>getServerSideProps()</strong>.
</p>
<p>
Using <strong>unstable_getServerSession()</strong> in{" "}
<strong>unstable_getServerSideProps()</strong> is the recommended
approach if you need to support Server Side Rendering with
authentication.
<strong>getServerSideProps()</strong> is the recommended approach if you
need to support Server Side Rendering with authentication.
</p>
<p>
The advantage of Server Side Rendering is this page does not require

View File

@@ -0,0 +1,12 @@
root = true
[*]
indent_size = 2
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

View File

@@ -0,0 +1,4 @@
dist
node_modules
tsconfig.json
package.json

View File

@@ -0,0 +1,10 @@
{
"extends": [
"@nuxtjs/eslint-config-typescript"
],
"rules": {
"@typescript-eslint/no-unused-vars": [
"off"
]
}
}

52
apps/playground-nuxt/.gitignore vendored Normal file
View File

@@ -0,0 +1,52 @@
# Dependencies
node_modules
# Logs
*.log*
# Temp directories
.temp
.tmp
.cache
# Yarn
**/.yarn/cache
**/.yarn/*state*
# Generated dirs
dist
# Nuxt
.nuxt
.output
.vercel_build_output
.build-*
.env
.netlify
# Env
.env
# Testing
reports
coverage
*.lcov
.nyc_output
# VSCode
.vscode
# Intellij idea
*.iml
.idea
# OSX
.DS_Store
.AppleDouble
.LSOverride
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
.vercel

View File

@@ -0,0 +1 @@
imports.autoImport=false

View File

@@ -0,0 +1,108 @@
# NextAuth + Nuxt 3 Playground
NextAuth.js is committed to bringing easy authentication to other frameworks. [#2294](https://github.com/nextauthjs/next-auth/issues/2294)
Nuxt 3 support with NextAuth.js is currently experimental. This directory contains a minimal, proof-of-concept application. Parts of this is expected to be abstracted away into a package like` @next-auth/nuxt.`
This package uses Nuxt's [module starter](https://github.com/nuxt/starter/tree/module).
Demo: https://next-auth-nuxt-demo.vercel.app
## Getting Started
### Add the module to the modules section of `nuxt.config.ts`:
```ts
export default defineNuxtConfig({
// temporary module name.
modules: ['next-auth-nuxt'],
// https://v3.nuxtjs.org/migration/runtime-config#runtime-config
runtimeConfig: {
secret: process.env.NEXTAUTH_SECRET
github: {
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET
}
},
// https://v3.nuxtjs.org/guide/concepts/esm#aliasing-libraries
// Fix for GithubProvider (or whichever provider you choose) is not a function error in Vite
alias: {
'next-auth/providers/github': 'node_modules/next-auth/providers/github.js'
}
})
```
### Add API route
To add `NextAuth.js` to a project create a file called `[...].ts` in `server/api/auth`. This contains the dynamic route handler for NextAuth.js which will also contain all of your global NextAuth.js configurations.
```ts
// ~/server/api/auth/[...].ts
import { NextAuthNuxtHandler } from 'next-auth-nuxt/handler'
import GithubProvider from 'next-auth/providers/github'
const runtimeConfig = useRuntimeConfig()
export const authOptions = {
secret: runtimeConfig.secret,
providers: [
GithubProvider({
clientId: runtimeConfig.github.clientId,
clientSecret: runtimeConfig.github.clientSecret
}),
],
}
export default NextAuthNuxtHandler(authOptions)
```
All requests to `/api/auth/*` (`signIn`, `callback`, `signOut`, etc.) will automatically be handled by NextAuth.js.
### Frontend - Add Vue Composable
The `useSession()` Vue Composable is the easiest way to check if someone is signed in.
```html
<script setup lang="ts">
const { data: session } = useSession()
</script>
<template>
<div v-if="session">
Signed in as {{ session.user.email }} <br />
<button @click="signOut">Sign out</button>
</div>
<div v-else>
Not signed in <br />
<button @click="signIn">Sign in</button>
</div>
</template>
```
### Backend - API Route
To protect an API Route, you can use the `getServerSession()` method.
```ts
import { getServerSession } from 'next-auth-nuxt/handler'
import { authOptions } from '~/server/api/auth/[...]'
export default defineEventHandler(async (event) => {
const session = await getServerSession(event, authOptions)
if (session) {
return {
content: 'This is protected content. You can access this content because you are signed in.'
}
}
return {
error: 'You must be signed in to view the protected content on this page.'
}
})
```
## Development
- Run `pnpm dev:generate` to generate type stubs.
- Use `pnpm dev` to start `playground` in development mode.

1
apps/playground-nuxt/client.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export * from './dist/runtime/client'

1
apps/playground-nuxt/handler.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export * from './dist/runtime/server/handler'

View File

@@ -0,0 +1,49 @@
{
"name": "next-auth-nuxt",
"type": "module",
"version": "0.0.0",
"packageManager": "pnpm@7.1.1",
"license": "MIT",
"main": "./dist/module.cjs",
"types": "./dist/types.d.ts",
"exports": {
".": {
"import": "./dist/module.mjs",
"require": "./dist/module.cjs"
},
"./handler": {
"import": "./dist/runtime/server/handler.mjs",
"types": "./dist/runtime/server/handler.d.ts"
},
"./client": {
"import": "./dist/runtime/client/index.mjs",
"types": "./dist/runtime/client/index.d.ts"
}
},
"files": [
"dist",
"handler.d.ts",
"client.d.ts"
],
"scripts": {
"prepack": "nuxt-module-build",
"dev": "pnpm prepack && nuxi dev playground",
"dev:build": "nuxi build playground",
"dev:build:vercel": "NITRO_PRESET=vercel nuxi build playground",
"dev:prepare": "nuxt-module-build --stub && nuxi prepare playground"
},
"dependencies": {
"@nuxt/kit": "^3.0.0-rc.13",
"h3": "^0.8.6",
"next-auth": "^4.16.2",
"pathe": "^0.3.9"
},
"devDependencies": {
"@nuxt/module-builder": "^0.2.0",
"@nuxt/schema": "^3.0.0-rc.12",
"@nuxtjs/eslint-config-typescript": "^11.0.0",
"eslint": "^8.26.0",
"nuxt": "^3.0.0-rc.13",
"next-auth-nuxt": "workspace:*"
}
}

View File

@@ -0,0 +1,4 @@
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
NEXTAUTH_URL=
NEXTAUTH_SECRET=

View File

@@ -0,0 +1,40 @@
<template>
<div>
<Header />
<NuxtPage />
<Footer />
</div>
</template>
<style>
body {
font-family: -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
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: .5rem;
filter: invert(1);
}
</style>

View File

@@ -0,0 +1,8 @@
<template>
<div>
<h1>Access Denied</h1>
<p>
<a href="/api/auth/signin">You must be signed in to view this page</a>
</p>
</div>
</template>

View File

@@ -0,0 +1,30 @@
<template>
<footer class="fotter">
<hr>
<ul class="navItems">
<li class="navItem">
<a href="https://github.com/nextauthjs/next-auth/tree/main/apps/playground-nuxt">Demo GitHub</a>
</li>
<li class="navItem">
<a href="https://next-auth.js.org">Next.js Documentation</a>
</li>
</ul>
</footer>
</template>
<style>
.footer {
margin-top: 2rem;
}
.navItems {
margin-bottom: 1rem;
padding: 0;
list-style: none;
}
.navItem {
display: inline-block;
margin-right: 1rem;
}
</style>

View File

@@ -0,0 +1,155 @@
<script setup lang="ts">
import { useSession, signIn, signOut, computed } from '#imports'
const { data: session, status } = useSession()
const loading = computed(() => status.value === 'loading')
</script>
<template>
<header>
<div class="signedInStatus">
<p :class="['nojs-show', !session && loading ? 'loading' : 'loaded']">
<template v-if="session">
<span v-if="session.user?.image" :style="{ backgroundImage: `url(${session.user.image})` }" class="avatar" />
<span class="signedInText">
<small>Signed in as</small><br>
<strong>{{ session.user?.email || session.user?.name }}</strong>
</span>
<a href="/api/auth/signout" class="button" @click.prevent="signOut">Sign out</a>
</template>
<template v-else>
<span class="notSignedInText">You are not signed in</span>
<a href="/api/auth/signin" class="buttonPrimary" @click.prevent="signIn">Sign in</a>
</template>
</p>
</div>
<nav>
<ul class="navItems">
<li class="navItem">
<NuxtLink to="/">
Home
</NuxtLink>
</li>
<li class="navItem">
<NuxtLink to="/client">
Client
</NuxtLink>
</li>
<li class="navItem">
<NuxtLink to="/server">
Server
</NuxtLink>
</li>
<li class="navItem">
<NuxtLink to="/protected">
Protected
</NuxtLink>
</li>
<li class="navItem">
<NuxtLink to="/api-example">
API
</NuxtLink>
</li>
</ul>
</nav>
</header>
</template>
<style>
.nojs-show {
opacity: 1;
top: 0;
}
.signedInStatus {
display: block;
min-height: 4rem;
width: 100%;
}
.loading,
.loaded {
position: relative;
top: 0;
opacity: 1;
overflow: hidden;
border-radius: 0 0 .6rem .6rem;
padding: .6rem 1rem;
margin: 0;
background-color: rgba(0,0,0,.05);
transition: all 0.2s ease-in;
}
.loading {
top: -2rem;
opacity: 0;
}
.signedInText,
.notSignedInText {
position: absolute;
padding-top: .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: -.4rem;
font-weight: 500;
border-radius: .3rem;
cursor: pointer;
font-size: 1rem;
line-height: 1.4rem;
padding: .7rem .8rem;
position: relative;
z-index: 10;
background-color: transparent;
color: #555;
}
.buttonPrimary {
background-color: #346df1;
border-color: #346df1;
color: #fff;
text-decoration: none;
padding: .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;
}
</style>

View File

@@ -0,0 +1,20 @@
import MyModule from '../src/module'
export default defineNuxtConfig({
modules: [
MyModule
],
// https://v3.nuxtjs.org/migration/runtime-config#runtime-config
runtimeConfig: {
secret: process.env.NEXTAUTH_SECRET,
github: {
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET
}
},
// https://v3.nuxtjs.org/guide/concepts/esm#aliasing-libraries
// Fix for GithubProvider is not a function error in Vite
alias: {
'next-auth/providers/github': 'node_modules/next-auth/providers/github.js'
}
})

View File

@@ -0,0 +1,4 @@
{
"name": "playground",
"private": true
}

View File

@@ -0,0 +1,15 @@
<template>
<div>
<h1>API Example</h1>
<p>The examples below show responses from the example API endpoints.</p>
<p>
<em>You must be signed in to see responses.</em>
</p>
<h2>Session</h2>
<p>/api/examples/session</p>
<iframe src="/api/examples/session" />
<h2>JSON Web Token</h2>
<p>/api/examples/jwt</p>
<iframe src="/api/examples/jwt" />
</div>
</template>

View File

@@ -0,0 +1,18 @@
<template>
<div>
<h1>Client Side Rendering</h1>
<p>
This page uses the <strong>useSession()</strong> Vue Composable in the <strong>&lt;Header/&gt;</strong> component.
</p>
<p>
The <strong>useSession()</strong> Vue Composable is easy to use and allows pages to render very quickly.
</p>
<p>
The advantage of this approach is that session state is shared between pages by using a provided session via <strong>Vue Plugin</strong> so
that navigation between pages using <strong>useSession()</strong> is very fast.
</p>
<p>
The disadvantage of <strong>useSession()</strong> is that it requires client side JavaScript.
</p>
</div>
</template>

View File

@@ -0,0 +1,8 @@
<template>
<div>
<h1>Nuxt 3 + NextAuth.js Example</h1>
<p>
This is an example site to demonstrate how to use <a href="https://v3.nuxtjs.org/">Nuxt 3</a> with <a href="https://next-auth.js.org">NextAuth.js</a> for authentication.
</p>
</div>
</template>

View File

@@ -0,0 +1,19 @@
<script setup lang="ts">
import { useSession, useFetch, useLazyFetch } from '#imports'
import AccessDenied from '~/components/AccessDenied.vue'
const { data: session } = useSession()
const { data } = await useLazyFetch('/api/examples/protected', {
server: false
})
</script>
<template>
<div>
<AccessDenied v-if="!session" />
<template v-else>
<h1>Protected Page</h1>
<p><strong>{{ data?.content || "\u00a0" }}</strong></p>
</template>
</div>
</template>

View File

@@ -0,0 +1,24 @@
<script setup lang="ts">
import { useFetch } from '#imports'
await useFetch('/api/examples/session')
</script>
<template>
<div>
<h1>Server Side Rendering</h1>
<p>
This page uses the <strong>getServerSession()</strong> method inside an api route and is fetched using the <strong>useFetch()</strong> composable.
</p>
<p>
Using <strong>getServerSession()</strong> is the recommended approach if you need to
support Server Side Rendering with authentication.
</p>
<p>
The advantage of Server Side Rendering is this page does not require client side JavaScript.
</p>
<p>
The disadvantage of Server Side Rendering is that this page is slower to render.
</p>
</div>
</template>

View File

@@ -0,0 +1,17 @@
import { NextAuthNuxtHandler } from 'next-auth-nuxt/handler'
import GithubProvider from 'next-auth/providers/github'
import type { NextAuthOptions } from 'next-auth'
const runtimeConfig = useRuntimeConfig()
export const authOptions: NextAuthOptions = {
secret: runtimeConfig.secret,
providers: [
GithubProvider({
clientId: runtimeConfig.github.clientId,
clientSecret: runtimeConfig.github.clientSecret
})
]
}
export default NextAuthNuxtHandler(authOptions)

View File

@@ -0,0 +1,10 @@
import { getToken } from 'next-auth/jwt'
export default defineEventHandler(async (event) => {
// @ts-expect-error: cookies property is not present in h3
event.req.cookies = parseCookies(event)
const token = await getToken({
req: event.req
})
return token
})

View File

@@ -0,0 +1,16 @@
import { getServerSession } from 'next-auth-nuxt/handler'
import { authOptions } from '../auth/[...]'
export default defineEventHandler(async (event) => {
const session = await getServerSession(event, authOptions)
if (session) {
return {
content: 'This is protected content. You can access this content because you are signed in.'
}
}
return {
error: 'You must be signed in to view the protected content on this page.'
}
})

View File

@@ -0,0 +1,7 @@
import { getServerSession } from 'next-auth-nuxt/handler'
import { authOptions } from '../auth/[...]'
export default defineEventHandler(async (event) => {
const session = await getServerSession(event, authOptions)
return session
})

6386
apps/playground-nuxt/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
packages:
- playground

View File

@@ -0,0 +1,40 @@
import { fileURLToPath } from 'url'
import { addImports, addPlugin, defineNuxtModule, extendViteConfig } from '@nuxt/kit'
import { resolve } from 'pathe'
export interface ModuleOptions {
}
export default defineNuxtModule<ModuleOptions>({
meta: {
name: 'next-auth-nuxt',
configKey: 'auth'
},
defaults: {
},
async setup (_options, nuxt) {
const runtimeDir = fileURLToPath(new URL('./runtime', import.meta.url))
nuxt.options.build.transpile.push(runtimeDir)
addPlugin(resolve(runtimeDir, 'plugin.client'))
// Composables are auto-imported in client.
const client = resolve(runtimeDir, 'client')
await addImports([
{ name: 'getSession', from: client },
{ name: 'getCsrfToken', from: client },
{ name: 'getProviders', from: client },
{ name: 'signIn', from: client },
{ name: 'signOut', from: client },
{ name: 'useSession', from: client }
])
// We can safely expose this to client.
extendViteConfig((config) => {
config.define = config.define || {}
config.define['process.env.NEXTAUTH_URL'] = JSON.stringify(process.env.NEXTAUTH_URL)
config.define['process.env.NEXTAUTH_URL_INTERNAL'] = JSON.stringify(process.env.NEXTAUTH_URL_INTERNAL)
config.define['process.env.VERCEL_URL'] = JSON.stringify(process.env.VERCEL_URL)
})
}
})

View File

@@ -0,0 +1,369 @@
import type { NextAuthClientConfig } from 'next-auth/client/_utils'
import type { Plugin, Ref } from 'vue'
import { ref, reactive, computed, inject, toRefs } from 'vue'
import { BroadcastChannel, apiBaseUrl, fetchData, now } from 'next-auth/client/_utils'
import type { Session } from 'next-auth'
import type {
BuiltInProviderType,
RedirectableProviderType
} from 'next-auth/providers'
import type { H3EventContext } from 'h3'
import parseUrl from '../lib/parse-url'
import _logger, { proxyLogger } from '../lib/logger'
import type {
ClientSafeProvider,
LiteralUnion,
SessionProviderProps,
SignInAuthorizationParams,
SignInOptions,
SignInResponse,
SignOutParams,
SignOutResponse
} from '../types'
// This behaviour mirrors the default behaviour for getting the site name that
// happens server side in server/index.js
// 1. An empty value is legitimate when the code is being invoked client side as
// relative URLs are valid in that context and so defaults to empty.
// 2. When invoked server side the value is picked up from an environment
// variable and defaults to 'http://localhost:3000'.
const __NEXTAUTH: NextAuthClientConfig = {
baseUrl: parseUrl(process.env.NEXTAUTH_URL ?? process.env.VERCEL_URL).origin,
basePath: parseUrl(process.env.NEXTAUTH_URL).path,
baseUrlServer: parseUrl(
process.env.NEXTAUTH_URL_INTERNAL ??
process.env.NEXTAUTH_URL ??
process.env.VERCEL_URL
).origin,
basePathServer: parseUrl(
process.env.NEXTAUTH_URL_INTERNAL ?? process.env.NEXTAUTH_URL
).path,
_lastSync: 0,
_session: undefined,
_getSession: () => {}
}
export interface CtxOrReq {
req?: H3EventContext['req']
event?: { req: H3EventContext['req'] }
}
export type GetSessionParams = CtxOrReq & {
event?: 'storage' | 'timer' | 'hidden' | string
triggerEvent?: boolean
broadcast?: boolean
}
const logger = proxyLogger(_logger, __NEXTAUTH.basePath)
const broadcast = BroadcastChannel()
function isServer () {
return (process as any).server
}
export async function getSession (params?: GetSessionParams) {
const session = await fetchData<Session>(
'session',
__NEXTAUTH,
logger,
params
)
if (params?.broadcast ?? true) { broadcast.post({ event: 'session', data: { trigger: 'getSession' } }) }
return session
}
/**
* Returns the current Cross Site Request Forgery Token (CSRF Token)
* required to make POST requests (e.g. for signing in and signing out).
* You likely only need to use this if you are not using the built-in
* `signIn()` and `signOut()` methods.
*
* [Documentation](https://next-auth.js.org/getting-started/client#getcsrftoken)
*/
export async function getCsrfToken (params?: CtxOrReq) {
const response = await fetchData<{ csrfToken: string }>(
'csrf',
__NEXTAUTH,
logger,
params
)
return response?.csrfToken
}
/**
* It calls `/api/auth/providers` and returns
* a list of the currently configured authentication providers.
* It can be useful if you are creating a dynamic custom sign in page.
*
* [Documentation](https://next-auth.js.org/getting-started/client#getproviders)
*/
export async function getProviders () {
return await fetchData<
Record<LiteralUnion<BuiltInProviderType>, ClientSafeProvider>
>('providers', __NEXTAUTH, logger)
}
/**
* Client-side method to initiate a signin flow
* or send the user to the signin page listing all possible providers.
* Automatically adds the CSRF token to the request.
*
* [Documentation](https://next-auth.js.org/getting-started/client#signin)
*/
export async function signIn<
P extends RedirectableProviderType | undefined = undefined,
> (
provider?: LiteralUnion<BuiltInProviderType>,
options?: SignInOptions,
authorizationParams?: SignInAuthorizationParams
): Promise<
P extends RedirectableProviderType ? SignInResponse | undefined : undefined
> {
const { callbackUrl = window.location.href, redirect = true } = options ?? {}
const baseUrl = apiBaseUrl(__NEXTAUTH)
const providers = await getProviders()
if (!providers) {
window.location.href = `${baseUrl}/error`
return
}
if (!provider || !(provider in providers)) {
window.location.href = `${baseUrl}/signin?${new URLSearchParams({
callbackUrl
})}`
return
}
const isCredentials = providers[provider].type === 'credentials'
const isEmail = providers[provider].type === 'email'
const isSupportingReturn = isCredentials || isEmail
const signInUrl = `${baseUrl}/${
isCredentials ? 'callback' : 'signin'
}/${provider}`
const _signInUrl = `${signInUrl}?${new URLSearchParams(authorizationParams)}`
const res = await fetch(_signInUrl, {
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
// @ts-expect-error: Internal
body: new URLSearchParams({
...options,
csrfToken: await getCsrfToken(),
callbackUrl,
json: true
})
})
const data = await res.json()
if (redirect || !isSupportingReturn) {
const url = data.url ?? callbackUrl
window.location.href = url
// If url contains a hash, the browser does not reload the page. We reload manually
if (url.includes('#')) { window.location.reload() }
return
}
const error = new URL(data.url).searchParams.get('error')
if (res.ok) { await __NEXTAUTH._getSession({ event: 'storage' }) }
return {
error,
status: res.status,
ok: res.ok,
url: error ? null : data.url
} as any
}
/**
* Signs the user out, by removing the session cookie.
* Automatically adds the CSRF token to the request.
*
* [Documentation](https://next-auth.js.org/getting-started/client#signout)
*/
export async function signOut<R extends boolean = true> (
options?: SignOutParams<R>
): Promise<R extends true ? undefined : SignOutResponse> {
const { callbackUrl = window.location.href } = options ?? {}
const baseUrl = apiBaseUrl(__NEXTAUTH)
const fetchOptions = {
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
// @ts-expect-error: Internal
body: new URLSearchParams({
csrfToken: await getCsrfToken(),
callbackUrl,
json: true
})
}
const res = await fetch(`${baseUrl}/signout`, fetchOptions)
const data = await res.json()
broadcast.post({ event: 'session', data: { trigger: 'signout' } })
if (options?.redirect ?? true) {
const url = data.url ?? callbackUrl
window.location.href = url
// If url contains a hash, the browser does not reload the page. We reload manually
if (url.includes('#')) { window.location.reload() }
// @ts-expect-error: Internal
return
}
await __NEXTAUTH._getSession({ event: 'storage' })
return data
}
export function SessionProviderPlugin (options: SessionProviderProps): Plugin {
return {
install (app) {
const { basePath } = options
if (basePath) { __NEXTAUTH.basePath = basePath }
/**
* If session was `null`, there was an attempt to fetch it,
* but it failed, but we still treat it as a valid initial value.
*/
const hasInitialSession = options.session !== undefined
/** If session was passed, initialize as already synced */
__NEXTAUTH._lastSync = hasInitialSession ? now() : 0
if (hasInitialSession) { __NEXTAUTH._session = options.session }
const session = ref(options.session)
/** If session was passed, initialize as not loading */
const loading = ref(!hasInitialSession)
__NEXTAUTH._getSession = async ({ event } = {}) => {
try {
const storageEvent = event === 'storage'
// We should always update if we don't have a client session yet
// or if there are events from other tabs/windows
if (storageEvent || __NEXTAUTH._session === undefined) {
__NEXTAUTH._lastSync = now()
__NEXTAUTH._session = await getSession({
broadcast: !storageEvent
})
session.value = __NEXTAUTH._session
return
}
if (
// If there is no time defined for when a session should be considered
// stale, then it's okay to use the value we have until an event is
// triggered which updates it
!event ||
// If the client doesn't have a session then we don't need to call
// the server to check if it does (if they have signed in via another
// tab or window that will come through as a "stroage" event
// event anyway)
__NEXTAUTH._session === null ||
// Bail out early if the client session is not stale yet
now() < __NEXTAUTH._lastSync
) { return }
// An event or session staleness occurred, update the client session.
__NEXTAUTH._lastSync = now()
__NEXTAUTH._session = await getSession()
session.value = __NEXTAUTH._session
} catch (error) {
logger.error('CLIENT_SESSION_ERROR', error as Error)
} finally {
loading.value = false
}
}
__NEXTAUTH._getSession()
const { refetchOnWindowFocus = true } = options
// Listen for when the page is visible, if the user switches tabs
// and makes our tab visible again, re-fetch the session, but only if
// this feature is not disabled.
const visibilityHandler = () => {
if (refetchOnWindowFocus && document.visibilityState === 'visible') { __NEXTAUTH._getSession({ event: 'visibilitychange' }) }
}
document.addEventListener('visibilitychange', visibilityHandler, false)
const unsubscribeFromBroadcast = broadcast.receive(() =>
__NEXTAUTH._getSession({ event: 'storage' })
)
const { refetchInterval } = options
let refetchIntervalTimer: NodeJS.Timer
if (refetchInterval) {
refetchIntervalTimer = setInterval(() => {
if (__NEXTAUTH._session) { __NEXTAUTH._getSession({ event: 'poll' }) }
}, refetchInterval * 1000)
}
const originalUnmount = app.unmount
app.unmount = function nextAuthUnmount () {
document.removeEventListener('visibilitychange', visibilityHandler, false)
unsubscribeFromBroadcast?.()
clearInterval(refetchIntervalTimer)
__NEXTAUTH._lastSync = 0
__NEXTAUTH._session = undefined
__NEXTAUTH._getSession = () => {}
originalUnmount()
}
const status = computed(() => loading.value ? 'loading' : session.value ? 'authenticated' : 'unauthenticated')
const value = reactive({
data: session,
status
})
app.provide('SessionKey', value)
}
}
}
/**
* Vue Composable that gives you access
* to the logged in user's session data.
*
* [Documentation](https://next-auth.js.org/getting-started/client#usesession)
*/
export function useSession (): {
data: Ref<SessionProviderProps['session']>;
status: Ref<string>;
} {
if (typeof window === 'undefined') {
return {
data: ref(null),
status: ref('loading')
}
}
const value = inject<{
data: SessionProviderProps['session']
status: string
}>('SessionKey')
if (!value) {
throw new Error('Could not resolve provided session value')
}
const { data, status } = toRefs(value)
return {
data,
status
}
}

View File

@@ -0,0 +1,115 @@
import type { Adapter } from 'next-auth/adapters'
import type { EventCallbacks, LoggerInstance } from 'next-auth'
/**
* Same as the default `Error`, but it is JSON serializable.
* @source https://iaincollins.medium.com/error-handling-in-javascript-a6172ccdf9af
*/
export class UnknownError extends Error {
code: string
constructor (error: Error | string) {
super((error as Error)?.message ?? error)
this.name = 'UnknownError'
this.code = (error as any).code
if (error instanceof Error) { this.stack = error.stack }
}
toJSON () {
return {
name: this.name,
message: this.message,
stack: this.stack
}
}
}
export class OAuthCallbackError extends UnknownError {
name = 'OAuthCallbackError'
}
/**
* Thrown when an Email address is already associated with an account
* but the user is trying an OAuth account that is not linked to it.
*/
export class AccountNotLinkedError extends UnknownError {
name = 'AccountNotLinkedError'
}
export class MissingAPIRoute extends UnknownError {
name = 'MissingAPIRouteError'
code = 'MISSING_NEXTAUTH_API_ROUTE_ERROR'
}
export class MissingSecret extends UnknownError {
name = 'MissingSecretError'
code = 'NO_SECRET'
}
export class MissingAuthorize extends UnknownError {
name = 'MissingAuthorizeError'
code = 'CALLBACK_CREDENTIALS_HANDLER_ERROR'
}
export class MissingAdapter extends UnknownError {
name = 'MissingAdapterError'
code = 'EMAIL_REQUIRES_ADAPTER_ERROR'
}
export class UnsupportedStrategy extends UnknownError {
name = 'UnsupportedStrategyError'
code = 'CALLBACK_CREDENTIALS_JWT_ERROR'
}
type Method = (...args: any[]) => Promise<any>
export function upperSnake (s: string) {
return s.replace(/([A-Z])/g, '_$1').toUpperCase()
}
export function capitalize (s: string) {
return `${s[0].toUpperCase()}${s.slice(1)}`
}
/**
* Wraps an object of methods and adds error handling.
*/
export function eventsErrorHandler (
methods: Partial<EventCallbacks>,
logger: LoggerInstance
): Partial<EventCallbacks> {
return Object.keys(methods).reduce<any>((acc, name) => {
acc[name] = async (...args: any[]) => {
try {
const method: Method = methods[name as keyof Method]
return await method(...args)
} catch (e) {
logger.error(`${upperSnake(name)}_EVENT_ERROR`, e as Error)
}
}
return acc
}, {})
}
/** Handles adapter induced errors. */
export function adapterErrorHandler (
adapter: Adapter | undefined,
logger: LoggerInstance
): Adapter | undefined {
if (!adapter) { return }
return Object.keys(adapter).reduce<any>((acc, name) => {
acc[name] = async (...args: any[]) => {
try {
logger.debug(`adapter_${name}`, { args })
const method: Method = adapter[name as keyof Method]
return await method(...args)
} catch (error) {
logger.error(`adapter_error_${name}`, error as Error)
const e = new UnknownError(error as Error)
e.name = `${capitalize(name)}Error`
throw e
}
}
return acc
}, {})
}

View File

@@ -0,0 +1,113 @@
import { UnknownError } from './errors'
// TODO: better typing
/** Makes sure that error is always serializable */
function formatError (o: unknown): unknown {
if (o instanceof Error && !(o instanceof UnknownError)) { return { message: o.message, stack: o.stack, name: o.name } }
if (hasErrorProperty(o)) {
o.error = formatError(o.error) as Error
o.message = o.message ?? o.error.message
}
return o
}
function hasErrorProperty (
x: unknown
): x is { error: Error; [key: string]: unknown } {
return !!(x as any)?.error
}
export type WarningCode =
| 'NEXTAUTH_URL'
| 'NO_SECRET'
| 'TWITTER_OAUTH_2_BETA'
| 'DEBUG_ENABLED'
/**
* Override any of the methods, and the rest will use the default logger.
*
* [Documentation](https://next-auth.js.org/configuration/options#logger)
*/
export interface LoggerInstance extends Record<string, Function> {
warn: (code: WarningCode) => void
error: (
code: string,
/**
* Either an instance of (JSON serializable) Error
* or an object that contains some debug information.
* (Error is still available through `metadata.error`)
*/
metadata: Error | { error: Error; [key: string]: unknown }
) => void
debug: (code: string, metadata: unknown) => void
}
const _logger: LoggerInstance = {
error (code, metadata) {
metadata = formatError(metadata) as Error
console.error(
`[next-auth][error][${code}]`,
`\nhttps://next-auth.js.org/errors#${code.toLowerCase()}`,
metadata.message,
metadata
)
},
warn (code) {
console.warn(
`[next-auth][warn][${code}]`,
`\nhttps://next-auth.js.org/warnings#${code.toLowerCase()}`
)
},
debug (code, metadata) {
// eslint-disable-next-line no-console
console.log(`[next-auth][debug][${code}]`, metadata)
}
}
/**
* Override the built-in logger with user's implementation.
* Any `undefined` level will use the default logger.
*/
export function setLogger (
newLogger: Partial<LoggerInstance> = {},
debug?: boolean
) {
// Turn off debug logging if `debug` isn't set to `true`
if (!debug) { _logger.debug = () => {} }
if (newLogger.error) { _logger.error = newLogger.error }
if (newLogger.warn) { _logger.warn = newLogger.warn }
if (newLogger.debug) { _logger.debug = newLogger.debug }
}
export default _logger
/** Serializes client-side log messages and sends them to the server */
export function proxyLogger (
logger: LoggerInstance = _logger,
basePath?: string
): LoggerInstance {
try {
if (typeof window === 'undefined') { return logger }
const clientLogger: Record<string, unknown> = {}
for (const level in logger) {
clientLogger[level] = (code: string, metadata: Error) => {
_logger[level](code, metadata) // Logs to console
if (level === 'error') {
metadata = formatError(metadata) as Error
}(metadata as any).client = true
const url = `${basePath}/_log`
const body = new URLSearchParams({ level, code, ...(metadata as any) })
if (navigator.sendBeacon) { return navigator.sendBeacon(url, body) }
return fetch(url, { method: 'POST', body, keepalive: true })
}
}
return clientLogger as unknown as LoggerInstance
} catch {
return _logger
}
}

View File

@@ -0,0 +1,34 @@
export interface InternalUrl {
/** @default "http://localhost:3000" */
origin: string
/** @default "localhost:3000" */
host: string
/** @default "/api/auth" */
path: string
/** @default "http://localhost:3000/api/auth" */
base: string
/** @default "http://localhost:3000/api/auth" */
toString: () => string
}
/** Returns an `URL` like object to make requests/redirects from server-side */
export default function parseUrl (url?: string): InternalUrl {
const defaultUrl = new URL('http://localhost:3000/api/auth')
if (url && !url.startsWith('http')) { url = `https://${url}` }
const _url = new URL(url ?? defaultUrl)
const path = (_url.pathname === '/' ? defaultUrl.pathname : _url.pathname)
// Remove trailing slash
.replace(/\/$/, '')
const base = `${_url.origin}${path}`
return {
origin: _url.origin,
host: _url.host,
path,
base,
toString: () => base
}
}

View File

@@ -0,0 +1,7 @@
// @ts-expect-error: Nuxt auto-import
import { defineNuxtPlugin } from '#app'
import { SessionProviderPlugin } from './client'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(SessionProviderPlugin({}))
})

View File

@@ -0,0 +1,93 @@
import type { NextAuthAction, NextAuthOptions, Session } from 'next-auth'
import type { RequestInternal } from 'next-auth/core'
import { NextAuthHandler } from 'next-auth/core'
import {
appendHeader,
defineEventHandler,
isMethod,
sendRedirect,
setCookie,
readBody,
parseCookies,
getQuery
} from 'h3'
import type { H3Event } from 'h3'
export function NextAuthNuxtHandler (options: NextAuthOptions) {
return defineEventHandler(async (event) => {
// Catch-all route params in Nuxt goes to the underscore property
const nextauth = event.context.params._.split('/')
const req: RequestInternal | Request = {
host: process.env.NEXTAUTH_URL,
body: undefined,
query: getQuery(event),
headers: event.req.headers,
method: event.req.method,
cookies: parseCookies(event),
action: nextauth[0] as NextAuthAction,
providerId: nextauth[1],
error: nextauth[1]
}
if (isMethod(event, 'POST')) {
req.body = await readBody(event)
}
const response = await NextAuthHandler({
req,
options
})
const { headers, cookies, body, redirect, status = 200 } = response
event.res.statusCode = status
headers?.forEach((header) => {
appendHeader(event, header.key, header.value)
})
cookies?.forEach((cookie) => {
setCookie(event, cookie.name, cookie.value, cookie.options)
})
if (redirect) {
if (isMethod(event, 'POST')) {
const body = await readBody(event)
if (body?.json !== 'true') { await sendRedirect(event, redirect, 302) }
return {
url: redirect
}
} else {
await sendRedirect(event, redirect, 302)
}
}
return body
})
}
export async function getServerSession (
event: H3Event,
options: NextAuthOptions
): Promise<Session | null> {
options.secret = process.env.NEXTAUTH_SECRET
const session = await NextAuthHandler<Session>({
req: {
host: process.env.NEXTAUTH_URL,
action: 'session',
method: 'GET',
cookies: parseCookies(event),
headers: event.req.headers
},
options
})
const { body } = session
if (body && Object.keys(body).length) {
return body
}
return null
}

View File

@@ -0,0 +1,78 @@
import type { Session } from 'next-auth'
import type { BuiltInProviderType, ProviderType } from 'next-auth/providers'
export interface UseSessionOptions<R extends boolean> {
required: R
/** Defaults to `signIn` */
onUnauthenticated?: () => void
}
/**
* Util type that matches some strings literally, but allows any other string as well.
* @source https://github.com/microsoft/TypeScript/issues/29729#issuecomment-832522611
*/
export type LiteralUnion<T extends U, U = string> =
| T
| (U & Record<never, never>)
export interface ClientSafeProvider {
id: LiteralUnion<BuiltInProviderType>
name: string
type: ProviderType
signinUrl: string
callbackUrl: string
}
export interface SignInOptions extends Record<string, unknown> {
/**
* Defaults to the current URL.
* @docs https://next-auth.js.org/getting-started/client#specifying-a-callbackurl
*/
callbackUrl?: string
/** @docs https://next-auth.js.org/getting-started/client#using-the-redirect-false-option */
redirect?: boolean
}
export interface SignInResponse {
error: string | undefined
status: number
ok: boolean
url: string | null
}
/** Match `inputType` of `new URLSearchParams(inputType)` */
export type SignInAuthorizationParams =
| string
| string[][]
| Record<string, string>
| URLSearchParams
/** @docs https://next-auth.js.org/getting-started/client#using-the-redirect-false-option-1 */
export interface SignOutResponse {
url: string
}
export interface SignOutParams<R extends boolean = true> {
/** @docs https://next-auth.js.org/getting-started/client#specifying-a-callbackurl-1 */
callbackUrl?: string
/** @docs https://next-auth.js.org/getting-started/client#using-the-redirect-false-option-1 */
redirect?: R
}
/** @docs: https://next-auth.js.org/getting-started/client#options */
export interface SessionProviderProps {
// children: React.ReactNode
session?: Session | null
baseUrl?: string
basePath?: string
/**
* A time interval (in seconds) after which the session will be re-fetched.
* If set to `0` (default), the session is not polled.
*/
refetchInterval?: number
/**
* `SessionProvider` automatically refetches the session when the user switches between windows.
* This option activates this behaviour if set to `true` (default).
*/
refetchOnWindowFocus?: boolean
}

View File

@@ -0,0 +1,4 @@
{
// https://v3.nuxtjs.org/concepts/typescript
"extends": "./playground/.nuxt/tsconfig.json"
}

View File

@@ -15,26 +15,23 @@ The MongoDB adapter does not handle connections automatically, so you will have
npm install next-auth @next-auth/mongodb-adapter mongodb
```
2. Add `lib/mongodb.js`
2. Add `lib/mongodb.ts`
```js
```ts
// This approach is taken from https://github.com/vercel/next.js/tree/canary/examples/with-mongodb
import { MongoClient } from "mongodb"
const uri = process.env.MONGODB_URI
const options = {
useUnifiedTopology: true,
useNewUrlParser: true,
}
let client
let clientPromise
import { MongoClient } from 'mongodb'
if (!process.env.MONGODB_URI) {
throw new Error("Please add your Mongo URI to .env.local")
throw new Error('Invalid/Missing environment variable: "MONGODB_URI"')
}
if (process.env.NODE_ENV === "development") {
const uri = process.env.MONGODB_URI
const options = {}
let client
let clientPromise: Promise<MongoClient>
if (process.env.NODE_ENV === 'development') {
// In development mode, use a global variable so that the value
// is preserved across module reloads caused by HMR (Hot Module Replacement).
if (!global._mongoClientPromise) {

View File

@@ -11,6 +11,7 @@ When using a database, you can still use JWT for session handling for fast acces
We have a list of official adapters that are distributed as their own packages under the `@next-auth/{name}-adapter` namespace. Their source code is available in their various adapters package directories at [`nextauthjs/next-auth`](https://github.com/nextauthjs/next-auth/tree/main/packages).
- [`xata`](./xata)
- [`prisma`](./prisma)
- [`fauna`](./fauna)
- [`dynamodb`](./dynamodb)
@@ -20,6 +21,7 @@ We have a list of official adapters that are distributed as their own packages u
- [`neo4j`](./neo4j)
- [`typeorm-legacy`](./typeorm)
- [`sequelize`](./sequelize)
- [`supabase`](./supabase)
- [`dgraph`](./dgraph)
- [`upstash-redis`](./upstash-redis)

View File

@@ -0,0 +1,309 @@
---
id: supabase
title: Supabase
---
# Supabase
This is the Supabase Adapter for [`next-auth`](https://next-auth.js.org). This package can only be used in conjunction with the primary `next-auth` package. It is not a standalone package.
:::note
This adapter is developed by the community and not officially maintained or supported by Supabase. It uses the Supabase Database to store user and session data in a separate `next_auth` schema. It is a standalone Auth server that does not interface with Supabase Auth and therefore provides a different feature set.
If youre looking for an officially maintained Auth server with additional features like [built-in email server](https://supabase.com/docs/guides/auth/auth-email#configure-email-settings?utm_source=next-auth-docs&medium=referral&campaign=next-auth), [phone auth](https://supabase.com/docs/guides/auth/auth-twilio?utm_source=next-auth-docs&medium=referral&campaign=next-auth), and [Multi Factor Authentication (MFA / 2FA)](https://supabase.com/contact/mfa?utm_source=next-auth-docs&medium=referral&campaign=next-auth), please use [Supabase Auth](https://supabase.com/auth) with the [Auth Helpers for Next.js](https://supabase.com/docs/guides/auth/auth-helpers/nextjs?utm_source=next-auth-docs&medium=referral&campaign=next-auth).
:::
## Getting Started
1. Install `@supabase/supabase-js`, `next-auth` and `@next-auth/supabase-adapter`.
```bash npm2yarn2pnpm
npm install @supabase/supabase-js next-auth @next-auth/supabase-adapter
```
2. Add this adapter to your `pages/api/[...nextauth].js` next-auth configuration object.
```js title="pages/api/auth/[...nextauth].js"
import NextAuth from "next-auth"
import { SupabaseAdapter } from "@next-auth/supabase-adapter"
// For more information on each option (and a full list of options) go to
// https://next-auth.js.org/configuration/options
export default NextAuth({
// https://next-auth.js.org/configuration/providers
providers: [...],
adapter: SupabaseAdapter({
url: process.env.NEXT_PUBLIC_SUPABASE_URL,
secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
}),
// ...
})
```
## Setup
### Create the `next_auth` schema in Supabase
Setup your database as described in our main [schema](/adapters/models), by copying the SQL schema below in the Supabase [SQL Editor](https://app.supabase.com/project/_/sql).
Alternatively you can select the NextAuth Quickstart card on the [SQL Editor page](https://app.supabase.com/project/_/sql), or [create a migration with the Supabase CLI](https://supabase.com/docs/guides/cli/local-development#database-migrations?utm_source=next-auth-docs&medium=referral&campaign=next-auth).
```sql
--
-- Name: next_auth; Type: SCHEMA;
--
CREATE SCHEMA next_auth;
GRANT USAGE ON SCHEMA next_auth TO service_role;
GRANT ALL ON SCHEMA next_auth TO postgres;
--
-- Create users table
--
CREATE TABLE IF NOT EXISTS next_auth.users
(
id uuid NOT NULL DEFAULT uuid_generate_v4(),
name text,
email text,
"emailVerified" timestamp with time zone,
image text,
CONSTRAINT users_pkey PRIMARY KEY (id),
CONSTRAINT email_unique UNIQUE (email)
);
GRANT ALL ON TABLE next_auth.users TO postgres;
GRANT ALL ON TABLE next_auth.users TO service_role;
--- uid() function to be used in RLS policies
CREATE FUNCTION next_auth.uid() RETURNS uuid
LANGUAGE sql STABLE
AS $$
select
coalesce(
nullif(current_setting('request.jwt.claim.sub', true), ''),
(nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'sub')
)::uuid
$$;
--
-- Create sessions table
--
CREATE TABLE IF NOT EXISTS next_auth.sessions
(
id uuid NOT NULL DEFAULT uuid_generate_v4(),
expires timestamp with time zone NOT NULL,
"sessionToken" text NOT NULL,
"userId" uuid,
CONSTRAINT sessions_pkey PRIMARY KEY (id),
CONSTRAINT sessionToken_unique UNIQUE ("sessionToken"),
CONSTRAINT "sessions_userId_fkey" FOREIGN KEY ("userId")
REFERENCES next_auth.users (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
);
GRANT ALL ON TABLE next_auth.sessions TO postgres;
GRANT ALL ON TABLE next_auth.sessions TO service_role;
--
-- Create accounts table
--
CREATE TABLE IF NOT EXISTS next_auth.accounts
(
id uuid NOT NULL DEFAULT uuid_generate_v4(),
type text NOT NULL,
provider text NOT NULL,
"providerAccountId" text NOT NULL,
refresh_token text,
access_token text,
expires_at bigint,
token_type text,
scope text,
id_token text,
session_state text,
oauth_token_secret text,
oauth_token text,
"userId" uuid,
CONSTRAINT accounts_pkey PRIMARY KEY (id),
CONSTRAINT provider_unique UNIQUE (provider, "providerAccountId"),
CONSTRAINT "accounts_userId_fkey" FOREIGN KEY ("userId")
REFERENCES next_auth.users (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
);
GRANT ALL ON TABLE next_auth.accounts TO postgres;
GRANT ALL ON TABLE next_auth.accounts TO service_role;
--
-- Create verification_tokens table
--
CREATE TABLE IF NOT EXISTS next_auth.verification_tokens
(
identifier text,
token text,
expires timestamp with time zone NOT NULL,
CONSTRAINT verification_tokens_pkey PRIMARY KEY (token),
CONSTRAINT token_unique UNIQUE (token),
CONSTRAINT token_identifier_unique UNIQUE (token, identifier)
);
GRANT ALL ON TABLE next_auth.verification_tokens TO postgres;
GRANT ALL ON TABLE next_auth.verification_tokens TO service_role;
```
### Expose the `next_auth` schema in Supabase
Expose the `next_auth` schema via the Serverless API in the [API settings](https://app.supabase.com/project/_/settings/api) by adding `next_auth` to the "Exposed schemas" list.
When developing locally add `next_auth` to the `schemas` array in the `config.toml` file in the `supabase` folder that was generated by the [Supabase CLI](https://supabase.com/docs/guides/cli/local-development#initialize-your-project?utm_source=next-auth-docs&medium=referral&campaign=next-auth).
## Enabling Row Level Security (RLS)
Postgres provides a powerful feature called [Row Level Security (RLS)](https://supabase.com/docs/guides/auth/row-level-security?utm_source=next-auth-docs&medium=referral&campaign=next-auth) to limit access to data.
This works by sending a signed JWT to your [Supabase Serverless API](https://supabase.com/docs/guides/api?utm_source=next-auth-docs&medium=referral&campaign=next-auth). There is two steps to make this work with NextAuth:
### 1. Generate the Supabase `access_token` JWT in the session callback
To sign the JWT use the `jsonwebtoken` package:
```bash npm2yarn2pnpm
npm install jsonwebtoken
```
Using the [NexthAuth Session callback](https://next-auth.js.org/configuration/callbacks#session-callback) create the Supabase `access_token` and append it to the `session` object.
To sign the JWT use the Supabase JWT secret which can be found in the [API settings](https://app.supabase.com/project/_/settings/api)
```js title="pages/api/auth/[...nextauth].js"
import NextAuth from "next-auth"
import { SupabaseAdapter } from "@next-auth/supabase-adapter"
import jwt from "jsonwebtoken"
// For more information on each option (and a full list of options) go to
// https://next-auth.js.org/configuration/options
export default NextAuth({
// https://next-auth.js.org/configuration/providers
providers: [...],
adapter: SupabaseAdapter({
url: process.env.NEXT_PUBLIC_SUPABASE_URL,
secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
}),
callbacks: {
async session({ session, user }) {
const signingSecret = process.env.SUPABASE_JWT_SECRET
if (signingSecret) {
const payload = {
aud: "authenticated",
exp: Math.floor(new Date(session.expires).getTime() / 1000),
sub: user.id,
email: user.email,
role: "authenticated",
}
session.supabaseAccessToken = jwt.sign(payload, signingSecret)
}
return session
},
},
// ...
})
```
### 2. Inject the Supabase `access_token` JWT into the Supabase Client
For example, given the following public schema:
```sql
/**
* USERS
* Note: This table contains user data. Users should only be able to view and update their own data.
*/
create table users (
-- UUID from next_auth.users
id uuid not null primary key,
name text,
email text,
image text,
constraint "users_id_fkey" foreign key ("id")
references next_auth.users (id) match simple
on update no action
on delete cascade -- if user is deleted in NextAuth they will also be deleted in our public table.
);
alter table users enable row level security;
create policy "Can view own user data." on users for select using (next_auth.uid() = id);
create policy "Can update own user data." on users for update using (next_auth.uid() = id);
/**
* This trigger automatically creates a user entry when a new user signs up via NextAuth.
*/
create function public.handle_new_user()
returns trigger as $$
begin
insert into public.users (id, name, email, image)
values (new.id, new.name, new.email, new.image);
return new;
end;
$$ language plpgsql security definer;
create trigger on_auth_user_created
after insert on next_auth.users
for each row execute procedure public.handle_new_user();
```
The `supabaseAccessToken` is now available on the `session` object and can be passed to the supabase-js client. This works in any environment: client-side, server-side (API routes, SSR), as well as in middleware edge functions!
```js
// ...
// Use `useSession()` or `unstable_getServerSession()` to get the NextAuth session.
const { supabaseAccessToken } = session
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{
global: {
headers: {
Authorization: `Bearer ${supabaseAccessToken}`,
},
},
}
)
// Now you can query with RLS enabled.
const { data, error } = await supabase.from("users").select("*")
```
## Usage with TypeScript
You can pass types that were [generated with the Supabase CLI](/docs/reference/javascript/typescript-support#generating-types) to the Supabase Client to get enhanced type safety and auto completion.
Creating a new supabase client object:
```tsx
import { createClient } from "@supabase/supabase-js"
import { Database } from "../database.types"
const supabase = createClient<Database>()
```
### Extend the session type with the `supabaseAccessToken`
In order to extend the `session` object with the `supabaseAccessToken` we need to extend the `session` interface in a `types/next-auth.d.ts` file:
```ts title="types/next-auth.d.ts"
import NextAuth, { DefaultSession } from "next-auth"
declare module "next-auth" {
/**
* Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
*/
interface Session {
// A JWT which can be used as Authorization header with supabase-js for RLS.
supabaseAccessToken?: string
user: {
/** The user's postal address. */
address: string
} & DefaultSession["user"]
}
}
```

242
docs/docs/adapters/xata.md Normal file
View File

@@ -0,0 +1,242 @@
---
id: xata
title: Xata
---
# Xata
This adapter allows using next-auth with Xata as a database to store users, sessions, and more. The preferred way to create a Xata project and use Xata databases is using the [Xata Command Line Interface (CLI)](https://docs.xata.io/cli/getting-started). The CLI allows generating a `XataClient` that will help you work with Xata in a safe way, and that this adapter depends on.
<!-- @todo add GIFs -->
## Getting Started
Let's first make sure we have everything installed and configured. We're going to need:
- next-auth + adapter
- the Xata CLI
- to configure the CLI
We can do this like so:
```bash npm2yarn2pnpm
# Install next-auth + adapter
npm install next-auth @next-auth/xata-adapter
# Install the Xata CLI globally if you don't already have it
npm install --location=global @xata.io/cli
# Login
xata auth login
```
Now that we're ready, let's create a new Xata project using our next-auth schema that the Xata adapter can work with. To do that, copy and paste this schema file into your project's directory:
```json title="schema.json"
{
"formatVersion": "",
"tables": [
{
"name": "nextauth_users",
"columns": [
{
"name": "email",
"type": "email"
},
{
"name": "emailVerified",
"type": "datetime"
},
{
"name": "name",
"type": "string"
},
{
"name": "image",
"type": "string"
}
]
},
{
"name": "nextauth_accounts",
"columns": [
{
"name": "user",
"type": "link",
"link": {
"table": "nextauth_users"
}
},
{
"name": "type",
"type": "string"
},
{
"name": "provider",
"type": "string"
},
{
"name": "providerAccountId",
"type": "string"
},
{
"name": "refresh_token",
"type": "string"
},
{
"name": "access_token",
"type": "string"
},
{
"name": "expires_at",
"type": "int"
},
{
"name": "token_type",
"type": "string"
},
{
"name": "scope",
"type": "string"
},
{
"name": "id_token",
"type": "text"
},
{
"name": "session_state",
"type": "string"
}
]
},
{
"name": "nextauth_verificationTokens",
"columns": [
{
"name": "identifier",
"type": "string"
},
{
"name": "token",
"type": "string"
},
{
"name": "expires",
"type": "datetime"
}
]
},
{
"name": "nextauth_users_accounts",
"columns": [
{
"name": "user",
"type": "link",
"link": {
"table": "nextauth_users"
}
},
{
"name": "account",
"type": "link",
"link": {
"table": "nextauth_accounts"
}
}
]
},
{
"name": "nextauth_users_sessions",
"columns": [
{
"name": "user",
"type": "link",
"link": {
"table": "nextauth_users"
}
},
{
"name": "session",
"type": "link",
"link": {
"table": "nextauth_sessions"
}
}
]
},
{
"name": "nextauth_sessions",
"columns": [
{
"name": "sessionToken",
"type": "string"
},
{
"name": "expires",
"type": "datetime"
},
{
"name": "user",
"type": "link",
"link": {
"table": "nextauth_users"
}
}
]
}
]
}
```
Now, run the following command:
```bash
xata init --schema=./path/to/your/schema.json
```
The CLI will walk you through a setup process where you choose a [workspace](https://docs.xata.io/concepts/workspaces) (kind of like a GitHub org or a Vercel team) and an appropriate database. We recommend using a fresh database for this, as we'll augment it with tables that next-auth needs.
Once you're done, you can continue using next-auth in your project as expected, like creating a `./pages/api/auth/[...nextauth]` route.
```typescript title="pages/api/auth/[...nextauth].ts"
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
const client = new XataClient()
export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
})
```
Now to Xata-fy this route, let's add the Xata client and adapter:
```diff
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
+import { XataAdapter } from "@next-auth/xata-adapter"
+import { XataClient } from "../../../xata" // or wherever you've chosen to create the client
+const client = new XataClient()
export default NextAuth({
+ adapter: XataAdapter(client),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
})
```
This fully sets up your next-auth site to work with Xata.
## Contributing
This is an open-source project created by humans, and as such, might have a few issues. If you experience any of these, we recommend [opening issues](https://github.com/nextauthjs/next-auth/issues/new?assignees=&labels=triage&template=1_bug_framework.yml&title=Issue%20on%20Xata%20adapter&description=I%20experienced%20this%20issue:\n##%20Reproduction%20Steps:\n\n-) that can help us solve problems and build reliable software.

View File

@@ -24,7 +24,7 @@ export const authOptions: NextAuthOptions = {
export default NextAuth(authOptions);
```
In `getServerSideProps`:
### In `getServerSideProps`:
```js
import { authOptions } from 'pages/api/auth/[...nextauth]'
import { unstable_getServerSession } from "next-auth/next"
@@ -48,7 +48,8 @@ export async function getServerSideProps(context) {
}
}
```
In API routes:
### In API Routes:
```js
import { authOptions } from 'pages/api/auth/[...nextauth]'
import { unstable_getServerSession } from "next-auth/next"
@@ -68,6 +69,23 @@ export async function handler(req, res) {
}
```
### In `app/` directory:
You can also use `unstable_getServerSession` in Next.js' server components:
```tsx
import { unstable_getServerSession } from "next-auth/next"
export default async function Page() {
const session = await unstable_getServerSession()
return <pre>{JSON.stringify(session, null, 2)}</pre>
}
```
:::warning
Currently, the underlying Next.js `cookies()` method does [only provides read access](https://beta.nextjs.org/docs/api-reference/cookies) to the request cookies. This means that the `expires` value is stripped away from `session` in Server Components. Furthermore, there is a hard expiry on sessions, after which the user will be required to sign in again. (The default expiry is 30 days).
:::
## Middleware
You can use a Next.js Middleware with NextAuth.js to protect your site.

View File

@@ -472,7 +472,8 @@ cookies: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: useSecureCookies
secure: useSecureCookies,
maxAge: 900
}
},
state: {
@@ -482,6 +483,7 @@ cookies: {
sameSite: "lax",
path: "/",
secure: useSecureCookies,
maxAge: 900
},
},
nonce: {

View File

@@ -13,7 +13,7 @@ Adding support for signing in via email in addition to one or more OAuth service
Configuration is similar to other providers, but the options are different:
```js title="pages/api/auth/[...nextauth].js"
import EmailProvider from `next-auth/providers/email`
import EmailProvider from "next-auth/providers/email"
...
providers: [
EmailProvider({

View File

@@ -83,7 +83,7 @@ TWITTER_SECRET=YOUR_TWITTER_CLIENT_SECRET
4. Now you can add the provider settings to the NextAuth.js options object. You can add as many OAuth providers as you like, as you can see `providers` is an array.
```js title="pages/api/auth/[...nextauth].js"
import TwitterProvider from "next-auth/providers/"
import TwitterProvider from "next-auth/providers/twitter"
...
providers: [
TwitterProvider({
@@ -174,6 +174,10 @@ interface OAuthConfig {
issuer?: string
client?: Partial<ClientMetadata>
allowDangerousEmailAccountLinking?: boolean
/**
* Object containing the settings for the styling of the providers sign-in buttons
*/
style: ProviderStyleType
}
```
@@ -281,7 +285,7 @@ An advanced option, hopefully you won't need it in most cases. `next-auth` uses
### `allowDangerousEmailAccountLinking` option
Normally, when you sign in with an OAuth provider and another account with the same email address already exists, the accounts are not linked automatically. Automatic account linking on sign in is not secure between arbitrary providers and is disabled by default (see our [Security FAQ](https://next-auth.js.org/faq#security)). However, it may be desirable to allow automatic account linking if you trust that the provider involved has securely verified the email address associated with the account. Just set `allowDangerousEmailAccountLinking: true` in your provider configuration to enable automatic account linking.
Normally, when you sign in with an OAuth provider and another account with the same email address already exists, the accounts are not linked automatically. Automatic account linking on sign in is not secure between arbitrary providers and is disabled by default (see our [Security FAQ](https://next-auth.js.org/faq#security)). However, it may be desirable to allow automatic account linking if you trust that the provider involved has securely verified the email address associated with the account. Just set `allowDangerousEmailAccountLinking: true` in your provider configuration to enable automatic account linking.
## Using a custom provider
@@ -425,10 +429,12 @@ GoogleProvider({
If you think your custom provider might be useful to others, we encourage you to open a PR and add it to the built-in list so others can discover it much more easily!
You only need to add two changes:
You only need to add three changes:
1. Add your config: [`src/providers/{provider}.ts`](https://github.com/nextauthjs/next-auth/tree/main/packages/next-auth/src/providers)<br />
• make sure you use a named default export, like this: `export default function YourProvider`
- Make sure you use a named default export, like this: `export default function YourProvider`
- Add two SVG's of the provider logo, like `google-dark.svg` (dark mode) and `google.svg` (light mode), to the `/packages/next-auth/provider-logos/` directory as well as the styling config to the provider config object. See existing provider for example
2. Add provider documentation: [`/docs/providers/{provider}.md`](https://github.com/nextauthjs/next-auth/tree/main/docs/docs/providers)
3. Add the new provider name to the `Provider type` dropdown options in [`the provider issue template`](<[http](https://github.com/nextauthjs/next-auth/edit/main/.github/ISSUE_TEMPLATE/2_bug_provider.yml)>)
That's it! 🎉 Others will be able to discover and use this provider much more easily now!

View File

@@ -44,8 +44,8 @@ providers: [
CredentialsProvider({
// The name to display on the sign in form (e.g. "Sign in with...")
name: "Credentials",
// The credentials is used to generate a suitable form on the sign in page.
// You can specify whatever fields you are expecting to be submitted.
// `credentials` is used to generate a form on the sign in page.
// You can specify which fields should be submitted, by adding keys to the `credentials` object.
// e.g. domain, username, password, 2FA token, etc.
// You can pass any HTML attribute to the <input> tag through the object.
credentials: {
@@ -54,7 +54,7 @@ providers: [
},
async authorize(credentials, req) {
// Add logic here to look up the user from the credentials supplied
const user = { id: 1, name: "J Smith", email: "jsmith@example.com" }
const user = { id: "1", name: "J Smith", email: "jsmith@example.com" }
if (user) {
// Any object returned will be saved in `user` property of the JWT

View File

@@ -11,7 +11,7 @@ http://developers.strava.com/docs/reference/
The **Strava Provider** comes with a set of default options:
- [Strava Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/strava.js)
- [Strava Provider options](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/providers/strava.ts)
You can override any of the options to suit your own use case. Ensure the redirect_uri configuration fits your needs accordingly.

View File

@@ -3,7 +3,7 @@ id: corporate-proxy
title: Add support for HTTP Proxy
--
Using NextAuth.js behind a corporate proxy is not supported out of the box. This is due to the fact that the underlying library we use, [`openid-client`](https://npm.im/openid-client), uses the built-in Node.js `http` / `https` libraries, which do not support proxys by default. (See: [`http` docs](https://nodejs.org/dist/latest-v16.x/docs/api/http.html), [`https` docs](https://nodejs.org/dist/latest-v16.x/docs/api/https.html)).
Using NextAuth.js behind a corporate proxy is not supported out of the box. This is due to the fact that the underlying library we use, [`openid-client`](https://npm.im/openid-client), uses the built-in Node.js `http` / `https` libraries, which do not support proxys by default. (See: [`http` docs](https://nodejs.org/dist/latest-v18.x/docs/api/http.html), [`https` docs](https://nodejs.org/dist/latest-v18.x/docs/api/https.html)).
Therefore, we'll need to an additional proxy agent to the http client, such as `https-proxy-agent`. `openid-client` allows the user to set an `agent` for requests ([Source](https://github.com/panva/node-openid-client/blob/main/docs/README.md#customizing-individual-http-requests).

View File

@@ -61,7 +61,7 @@ For the time being, the `withAuth` middleware only supports `"jwt"` as [session
More details can be found [here](https://next-auth.js.org/configuration/nextjs#middleware).
:::tip
To inclue all `dashboard` nested routes (sub pages like `/dashboard/settings`, `/dashboard/profile`) you can pass `matcher: "/dashboard/:path*"` to `config`.
To include all `dashboard` nested routes (sub pages like `/dashboard/settings`, `/dashboard/profile`) you can pass `matcher: "/dashboard/:path*"` to `config`.
For other patterns check out the [Next.js Middleware documentation](https://nextjs.org/docs/advanced-features/middleware#matcher).
:::

View File

@@ -65,6 +65,7 @@ module.exports = {
"adapters/neo4j",
"adapters/typeorm",
"adapters/sequelize",
"adapters/supabase",
"adapters/mikro-orm",
"adapters/dgraph",
"adapters/upstash-redis",

View File

@@ -1,8 +1,8 @@
# Corporate proxy
Using NextAuth.js behind a corporate proxy is not supported out of the box. This is due to the fact that the underlying library we use, [`openid-client`](https://npm.im/openid-client) which uses the built-in Node.js `http` / `https` libraries, and those do not support proxys by default:
- [`http` docs](https://nodejs.org/dist/latest-v16.x/docs/api/http.html)
- [`https` docs](https://nodejs.org/dist/latest-v16.x/docs/api/https.html)
- [`http` docs](https://nodejs.org/dist/latest-v18.x/docs/api/http.html)
- [`https` docs](https://nodejs.org/dist/latest-v18.x/docs/api/https.html)
Therefore, we'll need to add an additional proxy agent to the http client, such as `https-proxy-agent`.

View File

@@ -24,7 +24,7 @@ export const authOptions: NextAuthOptions = {
export default NextAuth(authOptions);
```
In `getServerSideProps`:
### In `getServerSideProps`:
```js
import { authOptions } from 'pages/api/[...nextauth]'
import { unstable_getServerSession } from "next-auth/next"
@@ -48,7 +48,8 @@ export async function getServerSideProps(context) {
}
}
```
In API routes:
### In API routes:
```js
import { authOptions } from 'pages/api/[...nextauth]'
import { unstable_getServerSession } from "next-auth/next"
@@ -68,6 +69,23 @@ export async function handler(req, res) {
}
```
### In `app/` directory:
You can also use `unstable_getServerSession` in Next.js' server components:
```tsx
import { unstable_getServerSession } from "next-auth/next"
export default async function Page() {
const session = await unstable_getServerSession()
return <pre>{JSON.stringify(session, null, 2)}</pre>
}
```
:::warning
Currently, the underlying Next.js `cookies()` method does [only provides read access](https://beta.nextjs.org/docs/api-reference/cookies) to the request cookies. This means that the `expires` value is stripped away from `session` in Server Components. Furthermore, there is a hard expiry on sessions, after which the user will be required to sign in again. (The default expiry is 30 days).
:::
## Middleware
You can use a Next.js Middleware with NextAuth.js to protect your site.

View File

@@ -40,7 +40,7 @@
"typescript": "4.8.4"
},
"engines": {
"node": "^12.19.0 || ^14.15.0 || ^16.13.0"
"node": "^12.19.0 || ^14.15.0 || ^16.13.0 || ^18.12.0"
},
"prettier": {
"semi": false,

View File

@@ -1,6 +1,6 @@
{
"name": "@next-auth/firebase-adapter",
"version": "1.0.2",
"version": "1.0.3",
"description": "Firebase adapter for next-auth.",
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth",
@@ -29,7 +29,7 @@
},
"scripts": {
"build": "tsc",
"test": "FIRESTORE_EMULATOR_HOST=localhost:8080 firebase emulators:exec --only firestore --project next-auth-test jest"
"test": "FIRESTORE_EMULATOR_HOST=localhost:8080 firebase --token '$FIREBASE_TOKEN' emulators:exec --only firestore --project next-auth-test jest"
},
"peerDependencies": {
"firebase": "^9.7.0",
@@ -38,9 +38,9 @@
"devDependencies": {
"@next-auth/adapter-test": "workspace:*",
"@next-auth/tsconfig": "workspace:*",
"firebase": "^9.7.0",
"firebase-tools": "^10.7.2",
"firebase": "^9.14.0",
"firebase-tools": "^11.16.1",
"jest": "^27.4.3",
"next-auth": "workspace:*"
}
}
}

View File

@@ -86,10 +86,10 @@ export function FirestoreAdapter({
async getUserByEmail(email) {
const userQuery = query(Users, where("email", "==", email), limit(1))
const userSnapshots = await getDocs(userQuery)
const userSnpashot = userSnapshots.docs[0]
const userSnapshot = userSnapshots.docs[0]
if (userSnpashot?.exists() && Users.converter) {
return Users.converter.fromFirestore(userSnpashot)
if (userSnapshot?.exists() && Users.converter) {
return Users.converter.fromFirestore(userSnapshot)
}
return null

View File

@@ -1,5 +1,5 @@
{
"extends": "@next-auth/tsconfig/tsconfig.base.json",
"extends": "@next-auth/tsconfig/tsconfig.adapters.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",

View File

@@ -1,6 +1,6 @@
{
"name": "@next-auth/pouchdb-adapter",
"version": "0.1.4",
"version": "0.1.5",
"description": "PouchDB adapter for next-auth.",
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth",

View File

@@ -1,5 +1,5 @@
{
"extends": "@next-auth/tsconfig/tsconfig.base.json",
"extends": "@next-auth/tsconfig/tsconfig.adapters.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",

View File

@@ -0,0 +1,3 @@
# Run `supabase start` in this directory to get the Supabase API URL and service role key!
SUPABASE_URL=http://localhost:54321
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSJ9.vI9obAHOGyVVKa3pD--kJlyxp-Z2zV9UUMAhKpNLAcU

View File

@@ -0,0 +1,57 @@
<p align="center">
<br/>
<a href="https://next-auth.js.org" target="_blank">
<img height="64px" src="https://next-auth.js.org/img/logo/logo-sm.png" /></a><img height="64px" src="./logo.svg" />
<h3 align="center"><b>Supabase Adapter</b> - NextAuth.js</h3>
<p align="center">
Open Source. Full Stack. Own Your Data.
</p>
<p align="center" style="align: center;">
<img src="https://github.com/nextauthjs/next-auth/actions/workflows/release.yml/badge.svg?branch=main" alt="Build Test" />
<img src="https://img.shields.io/bundlephobia/minzip/@next-auth/supabase-adapter/latest" alt="Bundle Size"/>
<img src="https://img.shields.io/npm/v/@next-auth/supabase-adapter" alt="@next-auth/supabase-adapter Version" />
</p>
</p>
## Overview
This is the Supabase Adapter for [`next-auth`](https://next-auth.js.org). This package can only be used in conjunction with the primary `next-auth` package. It is not a standalone package.
You can find more Supabase information in the docs at [next-auth.js.org/adapters/supabase](https://next-auth.js.org/adapters/supabase).
## Getting Started
1. Install `@supabase/supabase-js`, `next-auth` and `@next-auth/supabase-adapter`.
```js
npm install @supabase/supabase-js next-auth @next-auth/supabase-adapter
```
2. Add this adapter to your `pages/api/[...nextauth].js` next-auth configuration object.
```js
import NextAuth from "next-auth"
import { SupabaseAdapter } from "@next-auth/supabase-adapter"
// For more information on each option (and a full list of options) go to
// https://next-auth.js.org/configuration/options
export default NextAuth({
// https://next-auth.js.org/configuration/providers
providers: [
// ...
],
adapter: SupabaseAdapter({
url: process.env.NEXT_PUBLIC_SUPABASE_URL,
secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
}),
// ...
})
```
## Contributing
We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/next-auth/blob/main/CONTRIBUTING.md).
## License
ISC

View File

@@ -0,0 +1,15 @@
<svg width="109" height="113" viewBox="0 0 109 113" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0627L99.1935 40.0627C107.384 40.0627 111.952 49.5228 106.859 55.9374L63.7076 110.284Z" fill="url(#paint0_linear)"/>
<path d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0627L99.1935 40.0627C107.384 40.0627 111.952 49.5228 106.859 55.9374L63.7076 110.284Z" fill="url(#paint1_linear)" fill-opacity="0.2"/>
<path d="M45.317 2.07103C48.1765 -1.53037 53.9745 0.442937 54.0434 5.041L54.4849 72.2922H9.83113C1.64038 72.2922 -2.92775 62.8321 2.1655 56.4175L45.317 2.07103Z" fill="#3ECF8E"/>
<defs>
<linearGradient id="paint0_linear" x1="53.9738" y1="54.974" x2="94.1635" y2="71.8295" gradientUnits="userSpaceOnUse">
<stop stop-color="#249361"/>
<stop offset="1" stop-color="#3ECF8E"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="36.1558" y1="30.578" x2="54.4844" y2="65.0806" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,40 @@
{
"name": "@next-auth/supabase-adapter",
"version": "0.2.0",
"description": "Supabase adapter for next-auth.",
"homepage": "https://next-auth.js.org",
"repository": "https://github.com/nextauthjs/next-auth",
"bugs": {
"url": "https://github.com/nextauthjs/next-auth/issues"
},
"author": "Martin Sonnberger <martin.sonnberger@icloud.com>",
"main": "dist/index.js",
"keywords": [
"next-auth",
"next.js",
"supabase"
],
"license": "ISC",
"private": false,
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "tsc",
"test": "./tests/test.sh"
},
"peerDependencies": {
"@supabase/supabase-js": "^2.0.5",
"next-auth": "workspace:*"
},
"devDependencies": {
"@next-auth/adapter-test": "workspace:^0.0.0",
"@next-auth/tsconfig": "workspace:^0.0.0",
"@supabase/supabase-js": "^2.0.5",
"jest": "^27.4.3",
"next-auth": "workspace:*"
},
"jest": {
"preset": "@next-auth/adapter-test/jest"
}
}

View File

@@ -0,0 +1,140 @@
export type Json =
| string
| number
| boolean
| null
| { [key: string]: Json }
| Json[]
export interface Database {
next_auth: {
Tables: {
accounts: {
Row: {
id: string
type: string | null
provider: string | null
providerAccountId: string | null
refresh_token: string | null
access_token: string | null
expires_at: number | null
token_type: string | null
scope: string | null
id_token: string | null
session_state: string | null
oauth_token_secret: string | null
oauth_token: string | null
userId: string | null
}
Insert: {
id?: string
type?: string | null
provider?: string | null
providerAccountId?: string | null
refresh_token?: string | null
access_token?: string | null
expires_at?: number | null
token_type?: string | null
scope?: string | null
id_token?: string | null
session_state?: string | null
oauth_token_secret?: string | null
oauth_token?: string | null
userId?: string | null
}
Update: {
id?: string
type?: string | null
provider?: string | null
providerAccountId?: string | null
refresh_token?: string | null
access_token?: string | null
expires_at?: number | null
token_type?: string | null
scope?: string | null
id_token?: string | null
session_state?: string | null
oauth_token_secret?: string | null
oauth_token?: string | null
userId?: string | null
}
}
sessions: {
Row: {
expires: string | null
sessionToken: string | null
userId: string | null
id: string
}
Insert: {
expires?: string | null
sessionToken?: string | null
userId?: string | null
id?: string
}
Update: {
expires?: string | null
sessionToken?: string | null
userId?: string | null
id?: string
}
}
users: {
Row: {
name: string | null
email: string | null
emailVerified: string | null
image: string | null
id: string
}
Insert: {
name?: string | null
email?: string | null
emailVerified?: string | null
image?: string | null
id?: string
}
Update: {
name?: string | null
email?: string | null
emailVerified?: string | null
image?: string | null
id?: string
}
}
verification_tokens: {
Row: {
id: number
identifier: string | null
token: string | null
expires: string | null
}
Insert: {
id?: number
identifier?: string | null
token?: string | null
expires?: string | null
}
Update: {
id?: number
identifier?: string | null
token?: string | null
expires?: string | null
}
}
}
Views: {
[_ in never]: never
}
Functions: {
uid: {
Args: Record<PropertyKey, never>
Returns: string
}
}
Enums: {
[_ in never]: never
}
}
}

View File

@@ -0,0 +1,212 @@
import { createClient } from "@supabase/supabase-js"
import { Database } from "./database.types"
import {
Adapter,
AdapterSession,
AdapterUser,
VerificationToken,
} from "next-auth/adapters"
function isDate(date: any) {
return (
new Date(date).toString() !== "Invalid Date" && !isNaN(Date.parse(date))
)
}
export function format<T>(obj: Record<string, any>): T {
for (const [key, value] of Object.entries(obj)) {
if (value === null) {
delete obj[key]
}
if (isDate(value)) {
obj[key] = new Date(value)
}
}
return obj as T
}
export const SupabaseAdapter = ({
url,
secret,
}: {
url: string
secret: string
}): Adapter => {
const supabase = createClient<Database, "next_auth">(url, secret, {
db: { schema: "next_auth" },
global: {
headers: { "X-Client-Info": "@next-auth/supabase-adapter@0.1.0" },
},
})
return {
async createUser(user) {
const { data, error } = await supabase
.from("users")
.insert({
...user,
emailVerified: user.emailVerified?.toISOString(),
})
.select()
.single()
if (error) throw error
return format<AdapterUser>(data)
},
async getUser(id) {
const { data, error } = await supabase
.from("users")
.select()
.eq("id", id)
.maybeSingle()
if (error) throw error
if (!data) return null
return format<AdapterUser>(data)
},
async getUserByEmail(email) {
const { data, error } = await supabase
.from("users")
.select()
.eq("email", email)
.maybeSingle()
if (error) throw error
if (!data) return null
return format<AdapterUser>(data)
},
async getUserByAccount({ providerAccountId, provider }) {
const { data, error } = await supabase
.from("accounts")
.select("users (*)")
.match({ provider, providerAccountId })
.maybeSingle()
if (error) throw error
if (!data || !data.users) return null
return format<AdapterUser>(data.users)
},
async updateUser(user) {
const { data, error } = await supabase
.from("users")
.update({
...user,
emailVerified: user.emailVerified?.toISOString(),
})
.eq("id", user.id)
.select()
.single()
if (error) throw error
return format<AdapterUser>(data)
},
async deleteUser(userId) {
const { error } = await supabase.from("users").delete().eq("id", userId)
if (error) throw error
},
async linkAccount(account) {
const { error } = await supabase.from("accounts").insert(account)
if (error) throw error
},
async unlinkAccount({ providerAccountId, provider }) {
const { error } = await supabase
.from("accounts")
.delete()
.match({ provider, providerAccountId })
if (error) throw error
},
async createSession({ sessionToken, userId, expires }) {
const { data, error } = await supabase
.from("sessions")
.insert({ sessionToken, userId, expires: expires.toISOString() })
.select()
.single()
if (error) throw error
return format<AdapterSession>(data)
},
async getSessionAndUser(sessionToken) {
const { data, error } = await supabase
.from("sessions")
.select("*, users(*)")
.eq("sessionToken", sessionToken)
.maybeSingle()
if (error) throw error
if (!data) return null
const { users: user, ...session } = data
return {
user: format<AdapterUser>(
user as Database["next_auth"]["Tables"]["users"]["Row"]
),
session: format<AdapterSession>(session),
}
},
async updateSession(session) {
const { data, error } = await supabase
.from("sessions")
.update({
...session,
expires: session.expires?.toISOString(),
})
.eq("sessionToken", session.sessionToken)
.select()
.single()
if (error) throw error
return format<AdapterSession>(data)
},
async deleteSession(sessionToken) {
const { error } = await supabase
.from("sessions")
.delete()
.eq("sessionToken", sessionToken)
if (error) throw error
},
async createVerificationToken(token) {
const { data, error } = await supabase
.from("verification_tokens")
.insert({
...token,
expires: token.expires.toISOString(),
})
.select()
.single()
if (error) throw error
const { id, ...verificationToken } = data
return format<VerificationToken>(verificationToken)
},
async useVerificationToken({ identifier, token }) {
const { data, error } = await supabase
.from("verification_tokens")
.delete()
.match({ identifier, token })
.select()
.maybeSingle()
if (error) throw error
if (!data) return null
const { id, ...verificationToken } = data
return format<VerificationToken>(verificationToken)
},
}
}

View File

@@ -0,0 +1,44 @@
# A string used to distinguish different Supabase projects on the same host. Defaults to the working
# directory name when running `supabase init`.
project_id = "nextauth"
[api]
# Port to use for the API URL.
port = 54321
# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API
# endpoints. public and storage are always included.
schemas = ["next_auth"]
# Extra schemas to add to the search_path of every request.
extra_search_path = ["extensions"]
# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size
# for accidental or malicious requests.
max_rows = 1000
[db]
# Port to use for the local database URL.
port = 54322
# The database major version to use. This has to be the same as your remote database's. Run `SHOW
# server_version;` on the remote database to check.
major_version = 14
[studio]
# Port to use for Supabase Studio.
port = 54323
# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they
# are monitored, and you can view the emails that would have been sent from the web interface.
[inbucket]
# Port to use for the email testing server web interface.
port = 54324
[auth]
# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
# in emails.
site_url = "http://localhost:3000"
# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
additional_redirect_urls = ["https://localhost:3000"]
# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 seconds (one
# week).
jwt_expiry = 3600
# Allow/disallow new user signups to your project.
enable_signup = true

View File

@@ -0,0 +1,101 @@
--
-- Name: next_auth; Type: SCHEMA;
--
CREATE SCHEMA next_auth;
GRANT USAGE ON SCHEMA next_auth TO service_role;
GRANT ALL ON SCHEMA next_auth TO postgres;
--
-- Create users table
--
CREATE TABLE IF NOT EXISTS next_auth.users
(
id uuid NOT NULL DEFAULT uuid_generate_v4(),
name text,
email text,
"emailVerified" timestamp with time zone,
image text,
CONSTRAINT users_pkey PRIMARY KEY (id),
CONSTRAINT email_unique UNIQUE (email)
);
GRANT ALL ON TABLE next_auth.users TO postgres;
GRANT ALL ON TABLE next_auth.users TO service_role;
--- uid() function to be used in RLS policies
CREATE FUNCTION next_auth.uid() RETURNS uuid
LANGUAGE sql STABLE
AS $$
select
coalesce(
nullif(current_setting('request.jwt.claim.sub', true), ''),
(nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'sub')
)::uuid
$$;
--
-- Create sessions table
--
CREATE TABLE IF NOT EXISTS next_auth.sessions
(
id uuid NOT NULL DEFAULT uuid_generate_v4(),
expires timestamp with time zone NOT NULL,
"sessionToken" text NOT NULL,
"userId" uuid,
CONSTRAINT sessions_pkey PRIMARY KEY (id),
CONSTRAINT sessionToken_unique UNIQUE ("sessionToken"),
CONSTRAINT "sessions_userId_fkey" FOREIGN KEY ("userId")
REFERENCES next_auth.users (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
);
GRANT ALL ON TABLE next_auth.sessions TO postgres;
GRANT ALL ON TABLE next_auth.sessions TO service_role;
--
-- Create accounts table
--
CREATE TABLE IF NOT EXISTS next_auth.accounts
(
id uuid NOT NULL DEFAULT uuid_generate_v4(),
type text NOT NULL,
provider text NOT NULL,
"providerAccountId" text NOT NULL,
refresh_token text,
access_token text,
expires_at bigint,
token_type text,
scope text,
id_token text,
session_state text,
oauth_token_secret text,
oauth_token text,
"userId" uuid,
CONSTRAINT accounts_pkey PRIMARY KEY (id),
CONSTRAINT provider_unique UNIQUE (provider, "providerAccountId"),
CONSTRAINT "accounts_userId_fkey" FOREIGN KEY ("userId")
REFERENCES next_auth.users (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE
);
GRANT ALL ON TABLE next_auth.accounts TO postgres;
GRANT ALL ON TABLE next_auth.accounts TO service_role;
--
-- Create verification_tokens table
--
CREATE TABLE IF NOT EXISTS next_auth.verification_tokens
(
identifier text,
token text,
expires timestamp with time zone NOT NULL,
CONSTRAINT verification_tokens_pkey PRIMARY KEY (token),
CONSTRAINT token_unique UNIQUE (token),
CONSTRAINT token_identifier_unique UNIQUE (token, identifier)
);
GRANT ALL ON TABLE next_auth.verification_tokens TO postgres;
GRANT ALL ON TABLE next_auth.verification_tokens TO service_role;

View File

@@ -0,0 +1,33 @@
/**
* USERS
* Note: This table contains user data. Users should only be able to view and update their own data.
*/
create table users (
-- UUID from next_auth.users
id uuid not null primary key,
name text,
email text,
image text,
constraint "users_id_fkey" foreign key ("id")
references next_auth.users (id) match simple
on update no action
on delete cascade -- if user is deleted in NextAuth they will also be deleted in our public table.
);
alter table users enable row level security;
create policy "Can view own user data." on users for select using (next_auth.uid() = id);
create policy "Can update own user data." on users for update using (next_auth.uid() = id);
/**
* This trigger automatically creates a user entry when a new user signs up via NextAuth.
*/
create function public.handle_new_user()
returns trigger as $$
begin
insert into public.users (id, name, email, image)
values (new.id, new.name, new.email, new.image);
return new;
end;
$$ language plpgsql security definer;
create trigger on_auth_user_created
after insert on next_auth.users
for each row execute procedure public.handle_new_user();

View File

@@ -0,0 +1,76 @@
import { runBasicTests } from "@next-auth/adapter-test"
import { format, SupabaseAdapter } from "../src"
import { createClient } from "@supabase/supabase-js"
import type {
AdapterSession,
AdapterUser,
VerificationToken,
} from "next-auth/adapters"
import type { Account } from "next-auth"
const supabase = createClient(
process.env.SUPABASE_URL ?? "http://localhost:54321",
process.env.SUPABASE_SERVICE_ROLE_KEY ??
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSJ9.vI9obAHOGyVVKa3pD--kJlyxp-Z2zV9UUMAhKpNLAcU",
{ db: { schema: "next_auth" } }
)
runBasicTests({
adapter: SupabaseAdapter({
url: process.env.SUPABASE_URL ?? "http://localhost:54321",
secret:
process.env.SUPABASE_SERVICE_ROLE_KEY ??
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSJ9.vI9obAHOGyVVKa3pD--kJlyxp-Z2zV9UUMAhKpNLAcU",
}),
db: {
async session(sessionToken) {
const { data, error } = await supabase
.from("sessions")
.select()
.eq("sessionToken", sessionToken)
.maybeSingle()
if (error) throw error
if (!data) return null
return format<AdapterSession>(data)
},
async user(id) {
const { data, error } = await supabase
.from("users")
.select()
.eq("id", id)
.maybeSingle()
if (error) throw error
if (!data) return null
return format<AdapterUser>(data)
},
async account({ provider, providerAccountId }) {
const { data, error } = await supabase
.from("accounts")
.select()
.match({ provider, providerAccountId })
.maybeSingle()
if (error) throw error
if (!data) return null
return format<Account>(data)
},
async verificationToken({ identifier, token }) {
const { data, error } = await supabase
.from("verification_tokens")
.select()
.match({ identifier, token })
.single()
if (error) throw error
const { id, ...verificationToken } = data
return format<VerificationToken>(verificationToken)
},
},
})

View File

@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# install Supabase CLI when run on CI
if [ "$CI" = true ]; then
wget -O supabase.deb https://github.com/supabase/cli/releases/download/v0.29.0/supabase_0.29.0_linux_amd64.deb
sudo dpkg -i supabase.deb
fi
# Start Supabase, grep key and set it as SUPABASE_SERVICE_ROLE_KEY environment variable
line=$(supabase start | grep 'service_role key')
IFS=':'; arr=($line); unset IFS;
export SUPABASE_SERVICE_ROLE_KEY=${arr[1]}
# Always stop Supabase, but exit with 1 when tests are failing
if npx jest; then
supabase stop
else
supabase stop && exit 1
fi

View File

@@ -0,0 +1,8 @@
{
"extends": "@next-auth/tsconfig/tsconfig.adapters.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist"
},
"exclude": ["tests", "dist", "jest.config.js"]
}

View File

@@ -0,0 +1,249 @@
<p align="center">
<br/>
<a href="https://next-auth.js.org" target="_blank"><img height="64px" src="https://next-auth.js.org/img/logo/logo-sm.png" /></a>&nbsp;&nbsp;&nbsp;&nbsp;<img height="64px" src="logo.svg" />
<h3 align="center"><b>Xata Adapter</b> - NextAuth.js</h3>
<p align="center">
Think data, not databases.
</p>
<p align="center" style="align: center;">
<img src="https://github.com/nextauthjs/next-auth/actions/workflows/release.yml/badge.svg?branch=main" alt="CI Test" />
<a href="https://www.npmjs.com/package/@next-auth/xata-adapter" target="_blank"><img src="https://img.shields.io/bundlephobia/minzip/@next-auth/xata-adapter/next" alt="Bundle Size"/></a>
<a href="https://www.npmjs.com/package/@next-auth/xata-adapter" target="_blank"><img src="https://img.shields.io/npm/v/@next-auth/xata-adapter/next" alt="@next-auth/xata-adapter Version" /></a>
</p>
</p>
This adapter allows using next-auth with Xata as a database to store users, sessions, and more. The preferred way to create a Xata project and use Xata databases is using the [Xata Command Line Interface (CLI)](https://docs.xata.io/cli/getting-started). The CLI allows generating a `XataClient` that will help you work with Xata in a safe way, and that this adapter depends on.
<!-- @todo add GIFs -->
## Getting Started
Let's first make sure we have everything installed and configured. We're going to need:
- next-auth + adapter
- the Xata CLI
- to configure the CLI
We can do this like so:
```bash npm2yarn2pnpm
# Install next-auth + adapter
npm install next-auth @next-auth/xata-adapter
# Install the Xata CLI globally if you don't already have it
npm install --location=global @xata.io/cli
# Login
xata auth login
```
Now that we're ready, let's create a new Xata project using our next-auth schema that the Xata adapter can work with. To do that, copy and paste this schema file into your project's directory:
```json title="schema.json"
{
"formatVersion": "",
"tables": [
{
"name": "nextauth_users",
"columns": [
{
"name": "email",
"type": "email"
},
{
"name": "emailVerified",
"type": "datetime"
},
{
"name": "name",
"type": "string"
},
{
"name": "image",
"type": "string"
}
]
},
{
"name": "nextauth_accounts",
"columns": [
{
"name": "user",
"type": "link",
"link": {
"table": "nextauth_users"
}
},
{
"name": "type",
"type": "string"
},
{
"name": "provider",
"type": "string"
},
{
"name": "providerAccountId",
"type": "string"
},
{
"name": "refresh_token",
"type": "string"
},
{
"name": "access_token",
"type": "string"
},
{
"name": "expires_at",
"type": "int"
},
{
"name": "token_type",
"type": "string"
},
{
"name": "scope",
"type": "string"
},
{
"name": "id_token",
"type": "text"
},
{
"name": "session_state",
"type": "string"
}
]
},
{
"name": "nextauth_verificationTokens",
"columns": [
{
"name": "identifier",
"type": "string"
},
{
"name": "token",
"type": "string"
},
{
"name": "expires",
"type": "datetime"
}
]
},
{
"name": "nextauth_users_accounts",
"columns": [
{
"name": "user",
"type": "link",
"link": {
"table": "nextauth_users"
}
},
{
"name": "account",
"type": "link",
"link": {
"table": "nextauth_accounts"
}
}
]
},
{
"name": "nextauth_users_sessions",
"columns": [
{
"name": "user",
"type": "link",
"link": {
"table": "nextauth_users"
}
},
{
"name": "session",
"type": "link",
"link": {
"table": "nextauth_sessions"
}
}
]
},
{
"name": "nextauth_sessions",
"columns": [
{
"name": "sessionToken",
"type": "string"
},
{
"name": "expires",
"type": "datetime"
},
{
"name": "user",
"type": "link",
"link": {
"table": "nextauth_users"
}
}
]
}
]
}
```
Now, run the following command:
```bash
xata init --schema=./path/to/your/schema.json
```
The CLI will walk you through a setup process where you choose a [workspace](https://docs.xata.io/concepts/workspaces) (kind of like a GitHub org or a Vercel team) and an appropriate database. We recommend using a fresh database for this, as we'll augment it with tables that next-auth needs.
Once you're done, you can continue using next-auth in your project as expected, like creating a `./pages/api/auth/[...nextauth]` route.
```typescript title="pages/api/auth/[...nextauth].ts"
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
const client = new XataClient()
export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
})
```
Now to Xata-fy this route, let's add the Xata client and adapter:
```diff
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
+import { XataAdapter } from "@next-auth/xata-adapter"
+import { XataClient } from "../../../xata" // or wherever you've chosen to create the client
+const client = new XataClient()
export default NextAuth({
+ adapter: XataAdapter(client),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
})
```
This fully sets up your next-auth site to work with Xata.
## Contributing
This is an open-source project created by humans, and as such, might have a few issues. If you experience any of these, we recommend [opening issues](https://github.com/nextauthjs/next-auth/issues/new?assignees=&labels=triage&template=1_bug_framework.yml&title=Issue%20on%20Xata%20adapter&description=I%20experienced%20this%20issue:\n##%20Reproduction%20Steps:\n\n-) that can help us solve problems and build reliable software.

View File

@@ -0,0 +1,6 @@
<svg width="461" height="467" viewBox="0 0 461 467" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M389.431 465.611C421.673 433.443 443.246 393.222 449.403 353.796C455.56 314.369 445.797 278.966 422.262 255.376L300.691 376.663L389.431 465.611Z" fill="#06D6A0"/>
<path d="M71.5738 466.306C39.331 434.138 17.7582 393.917 11.6011 354.49C5.44395 315.064 15.2069 279.661 38.7422 256.07L160.314 377.358L71.5738 466.306Z" fill="#FFBB00"/>
<path d="M0.673946 122.184C0.727229 167.729 18.8709 211.387 51.1137 243.554L51.1168 243.551L172.673 364.823C204.84 332.58 222.882 288.88 222.829 243.335C222.775 197.79 204.632 154.132 172.389 121.964L172.386 121.967L50.8296 0.695312C18.6622 32.9381 0.620662 76.6388 0.673946 122.184Z" fill="#FF0075"/>
<path d="M460.329 121.488C460.276 167.033 442.132 210.692 409.89 242.859L409.886 242.855L288.33 364.127C256.163 331.884 238.121 288.183 238.175 242.638C238.228 197.094 256.372 153.435 288.614 121.268L288.618 121.271L410.174 0C442.341 32.2428 460.383 75.9435 460.329 121.488Z" fill="#0076FF"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

Some files were not shown because too many files have changed in this diff Show More