Compare commits

..

22 Commits

Author SHA1 Message Date
ndom91
7e2ff3ee72 chore: rename _app.js file in sandpack 2023-02-18 20:41:39 +01:00
ndom91
9b6bcfe2ce feat: sandpack v2 test 2023-02-18 20:36:09 +01:00
ndom91
26e2f795d8 fix: refactor all to Callout 2023-02-18 18:44:41 +01:00
ndom91
d5eade5ef4 fix: add _meta.json for all sidebar categories and start adding Callouts 2023-02-18 18:12:56 +01:00
Balázs Orbán
2fab7d4cd1 update typedoc-plugin-markdown 2023-01-30 01:57:10 +01:00
Balázs Orbán
177fa44a49 fix formatting, and relative links in nextra 2023-01-19 11:21:03 +01:00
Balázs Orbán
92657bade3 use patched typedoc markdown plugin 2023-01-18 14:42:12 +01:00
Balázs Orbán
581d37b50f Merge branch 'main' into ndom91/docs-rewrite-links 2023-01-18 11:39:38 +01:00
Balázs Orbán
1a928949ce bump nextra 2023-01-18 11:34:55 +01:00
Balázs Orbán
8f222b2e56 fixes 2023-01-16 15:46:07 +01:00
ndom91
01a78e1282 chore: WIP duration hack 2023-01-16 01:20:21 +01:00
ndom91
be481e6fe6 feat: marquee animations 2023-01-16 00:40:52 +01:00
ndom91
e1f5fe3e6d feat: new blob svgs 2023-01-15 23:59:40 +01:00
ndom91
40f754fbca feat: blob design + marquee 2023-01-15 23:46:58 +01:00
ndom91
cf236c6bf3 feat: style landing page 2023-01-15 22:36:13 +01:00
ndom91
b7a38ec7b1 chore: add blob assets 2023-01-15 21:43:45 +01:00
ndom91
db26a4c18d feat: add tailwind and homepage starter 2023-01-15 17:56:50 +01:00
Balázs Orbán
42b55f44f4 remove ignored file 2023-01-13 17:09:37 +01:00
Balázs Orbán
1d3761356a fixes 2023-01-13 17:08:08 +01:00
ndom91
79147f1cf5 fix: typedoc markdown plugin 2023-01-12 22:48:43 +01:00
ndom91
caf63f0eb4 chore(docs): copy over docs 2023-01-12 22:42:43 +01:00
ndom91
7dec2eec71 chore(docs): test nextra docs 2023-01-12 22:13:26 +01:00
905 changed files with 36296 additions and 26039 deletions

70
.eslintignore Normal file
View File

@@ -0,0 +1,70 @@
.eslintrc.js
.cache-loader
.DS_Store
.pnpm-debug.log
.turbo
.vscode/generated*
/_work
/actions-runner
node_modules
patches
pnpm-lock.yaml
.github/actions/issue-validator/index.mjs
*.cjs
*.js
*.d.ts
*.d.ts.map
.svelte-kit
.next
.nuxt
# --------------- Docs ---------------
.docusaurus
build
docs/docs/reference/03-core
docs/docs/reference/04-sveltekit
static
# --------------- Packages ---------------
coverage
dist
# @auth/core
packages/core/src/providers/oauth-types.ts
packages/core/src/lib/pages/styles.ts
# @auth/sveltekit
packages/frameworks-sveltekit/package
packages/frameworks-sveltekit/vite.config.{js,ts}.timestamp-*
# next-auth
packages/next-auth/src/providers/oauth-types.ts
packages/next-auth/css/index.css
# Adapters
.branches
db.sqlite
dev.db
dynamodblocal-bin
firebase-debug.log
firestore-debug.log
migrations
test.schema.gql
# --------------- Apps ---------------
# Examples should have their own Prettier config since they are templates too
apps/example-sveltekit
# Development app
apps
# --------------- Tests ---------------
# TODO: these should be linted
packages/**/*test*

75
.eslintrc.js Normal file
View File

@@ -0,0 +1,75 @@
// @ts-check
/** @type {import("eslint").ESLint.ConfigData} */
module.exports = {
env: { browser: true, es2022: true, node: true },
extends: ["eslint:recommended", "prettier"],
overrides: [
{
files: ["*.ts", "*.tsx"],
parser: "@typescript-eslint/parser",
parserOptions: {
project: ["./packages/**/tsconfig.json", "./apps/**/tsconfig.json"],
},
settings: { react: { version: "18" } },
extends: [
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"standard-with-typescript",
"prettier",
],
rules: {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/method-signature-style": "off",
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/restrict-template-expressions": "off",
"@typescript-eslint/strict-boolean-expressions": "off",
"react/prop-types": "off",
"react/no-unescaped-entities": "off",
},
},
{
files: ["*.test.ts", "*.test.js"],
extends: ["plugin:jest/recommended"],
env: { jest: true },
},
// {
// files: ["docs/**"],
// plugins: ["@docusaurus"],
// extends: ["plugin:@docusaurus/recommended"],
// },
{
// TODO: Expand to all packages
files: ["packages/{core,sveltekit}/*.ts"],
plugins: ["jsdoc"],
extends: ["plugin:jsdoc/recommended"],
rules: {
"jsdoc/require-param": "off",
"jsdoc/require-returns": "off",
"jsdoc/require-jsdoc": [
"warn",
{ publicOnly: true, enableFixer: false },
],
"jsdoc/no-multi-asterisks": ["warn", { allowWhitespace: true }],
"jsdoc/tag-lines": "off",
},
},
{
files: ["packages/frameworks-sveltekit"],
plugins: ["svelte3"],
overrides: [{ files: ["*.svelte"], processor: "svelte3/svelte3" }],
settings: {
"svelte3/typescript": () => require("typescript"),
},
parserOptions: { sourceType: "module", ecmaVersion: 2020 },
env: { browser: true, es2017: true, node: true },
},
],
parserOptions: {
sourceType: "module",
ecmaVersion: "latest",
ecmaFeatures: { jsx: true },
},
root: true,
}

View File

@@ -30,7 +30,7 @@ body:
Run this command in your project's root folder and paste the result:
```sh
npx envinfo --system --binaries --browsers --npmPackages "next,react,next-auth,@auth/*"
npx envinfo --system --binaries --browsers --npmPackages "next,react,next-auth"
```
Alternatively, you can manually gather the version information from your package.json for these packages: "next", "react" and "next-auth". Please also mention your OS and Node.js version, as well as the browser you are using.
validations:

View File

@@ -25,19 +25,16 @@ body:
- "Custom provider"
- "42 School"
- "Apple"
- "Asgardeo"
- "Atlassian"
- "Auth0"
- "Authentik"
- "Azure Active Directory"
- "Azure Active Directory B2C"
- "Battlenet"
- "Beyond Identity"
- "Box"
- "Bungie"
- "Cognito"
- "Coinbase"
- "Descope"
- "Discord"
- "Dropbox"
- "EVE Online"
@@ -60,7 +57,6 @@ body:
- "Medium"
- "Naver"
- "Netlify"
- "Notion"
- "Okta"
- "OneLogin"
- "Osso"
@@ -91,7 +87,7 @@ body:
Run this command in your project's root folder and paste the result:
```sh
npx envinfo --system --binaries --browsers --npmPackages "next,react,next-auth,@auth/*"
npx envinfo --system --binaries --browsers --npmPackages "next,react,next-auth"
```
Alternatively, you can manually gather the version information from your package.json for these packages: "next", "react" and "next-auth". Please also mention your OS and Node.js version, as well as the browser you are using.
validations:

View File

@@ -21,23 +21,20 @@ body:
multiple: true
options:
- "Custom adapter"
- "@auth/dgraph-adapter"
- "@auth/drizzle-adapter"
- "@auth/dynamodb-adapter"
- "@auth/drizzle-adapter"
- "@auth/fauna-adapter"
- "@auth/firebase-adapter"
- "@auth/kysely-adapter"
- "@auth/mikro-orm-adapter"
- "@auth/mongodb-adapter"
- "@auth/neo4j-adapter"
- "@auth/pouchdb-adapter"
- "@auth/prisma-adapter"
- "@auth/sequelize-adapter"
- "@auth/supabase-adapter"
- "@auth/typeorm-adapter"
- "@auth/upstash-redis-adapter"
- "@auth/xata-adapter"
- "@next-auth/dgraph-adapter"
- "@next-auth/dynamodb-adapter"
- "@next-auth/fauna-adapter"
- "@next-auth/firebase-adapter"
- "@next-auth/mikro-orm-adapter"
- "@next-auth/mongodb-adapter"
- "@next-auth/neo4j-adapter"
- "@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
@@ -47,7 +44,7 @@ body:
Run this command in your project's root folder and paste the result:
```sh
npx envinfo --system --binaries --browsers --npmPackages "next,react,next-auth,@auth/*" && npx envinfo --npmPackages "@next-auth/*"
npx envinfo --system --binaries --browsers --npmPackages "next,react,next-auth" && npx envinfo --npmPackages "@next-auth/*"
```
Alternatively, if the above command did not work, we need the version of the following packages from your package.json: "next", "react", "next-auth" and your adapter. Please also mention your OS and Node.js version, as well as the browser you are using.
validations:

View File

@@ -14,9 +14,9 @@ Ensure the link is pointing to a codebase that is accessible (e.g. not a private
### **What happens if I don't provide a sufficient minimal reproduction?**
Issues with the `incomplete` label that receives no meaningful activity (e.g. new comments with a reproduction link) are closed after 7 days.
Issues with the `incomplete` label that receives no meaningful activity (e.g. new comments with a reproduction link) are automatically closed and locked after 30 days.
If your issue has _not_ been resolved in that time and it has been closed/locked, please open a new issue with the required reproduction. (It's less likely that we check back on already closed issues.)
If your issue has _not_ been resolved in that time and it has been closed/locked, please open a new issue with the required reproduction.
### **I did not open this issue, but it is relevant to me, what can I do to help?**

View File

@@ -1,49 +1,43 @@
# https://github.com/github/issue-labeler#basic-examples
dgraph:
- "@auth/dgraph-adapter"
drizzle:
- "@auth/drizzle-adapter"
- "@next-auth/dgraph-adapter"
dynamodb:
- "@auth/dynamodb-adapter"
- "@next-auth/dynamodb-adapter"
fauna:
- "@auth/fauna-adapter"
- "@next-auth/fauna-adapter"
firebase:
- "@auth/firebase-adapter"
kysely:
- "@auth/kysely-adapter"
- "@next-auth/firebase-adapter"
mikro-orm:
- "@auth/mikro-orm-adapter"
- "@next-auth/mikro-orm-adapter"
mongodb:
- "@auth/mongodb-adapter"
- "@next-auth/mongodb-adapter"
neo4j:
- "@auth/neo4j-adapter"
- "@next-auth/neo4j-adapter"
pouchdb:
- "@auth/pouchdb-adapter"
- "@next-auth/pouchdb-adapter"
prisma:
- "@auth/prisma-adapter"
- "@next-auth/prisma-adapter"
sequelize:
- "@auth/sequelize-adapter"
- "@next-auth/sequelize-adapter"
supabase:
- "@auth/supabase-adapter"
- "@next-auth/supabase-adapter"
typeorm:
- "@auth/typeorm-adapter"
typeorm-legacy:
- "@next-auth/typeorm-legacy-adapter"
upstash-redis:
- "@auth/upstash-redis-adapter"
- "@next-auth/upstash-redis-adapter"
xata:
- "@auth/xata-adapter"
- "@next-auth/xata-adapter"

View File

@@ -15,13 +15,12 @@ neo4j: ["packages/adapter-neo4j/**/*"]
playgrounds: ["apps/playgrounds/**/*"]
pouchdb: ["packages/adapter-pouchdb/**/*"]
prisma: ["packages/adapter-prisma/**/*"]
kysely: ["packages/adapter-kysely/**/*"]
providers: ["packages/core/src/providers/**/*"]
sequelize: ["packages/adapter-sequelize/**/*"]
solidjs: ["packages/frameworks-solid-start/**/*"]
supabase: ["packages/adapter-supabase/**/*"]
svelte: ["packages/frameworks-sveltekit/**/*"]
test: ["**test**/*"]
typeorm: ["packages/adapter-typeorm/**/*"]
typeorm-legacy: ["packages/adapter-typeorm-legacy/**/*"]
upstash-redis: ["packages/adapter-upstash-redis/**/*"]
xata: ["packages/adapter-xata/**/*"]

9
.github/sync.yml vendored
View File

@@ -1,3 +1,5 @@
# Note that nextauthjs/next-auth-example syncs from the v4 branch
nextauthjs/sveltekit-auth-example:
- source: apps/examples/sveltekit
dest: .
@@ -18,10 +20,3 @@ nextauthjs/next-auth-gatsby-example:
deleteOrphaned: true
- .github/FUNDING.yml
- LICENSE
nextauthjs/next-auth-example:
- source: apps/examples/nextjs
dest: .
deleteOrphaned: true
- .github/FUNDING.yml
- LICENSE

View File

@@ -5,15 +5,14 @@ const core = require("@actions/core")
try {
const packageJSONPath = path.join(
process.cwd(),
`packages/${process.env.PACKAGE_PATH || "next-auth"}/package.json`
"packages/next-auth/package.json"
)
const packageJSON = JSON.parse(fs.readFileSync(packageJSONPath, "utf8"))
const sha8 = process.env.GITHUB_SHA.substring(0, 8)
const prefix = "0.0.0-"
const pr = process.env.PR_NUMBER
const source = pr ? `pr.${pr}` : "manual"
const packageVersion = `${prefix}${source}.${sha8}`
const prNumber = process.env.PR_NUMBER
const packageVersion = `0.0.0-pr.${prNumber}.${sha8}`
packageJSON.version = packageVersion
core.setOutput("version", packageVersion)
fs.writeFileSync(packageJSONPath, JSON.stringify(packageJSON))

View File

@@ -11,7 +11,7 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Run issue validator
- name: "Run issue validator"
run: node /home/runner/work/next-auth/next-auth/.github/actions/issue-validator/index.mjs
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -3,61 +3,11 @@ name: Release
on:
push:
branches:
- main
- beta
- next
- 3.x
- "main"
- "beta"
- "next"
- "3.x"
pull_request:
# TODO: Support latest releases
workflow_dispatch:
inputs:
name:
type: choice
description: Package name (npm)
options:
- "@auth/core"
- "@auth/nextjs"
- "@auth/dgraph-adapter"
- "@auth/drizzle-adapter"
- "@auth/dynamodb-adapter"
- "@auth/fauna-adapter"
- "@auth/firebase-adapter"
- "@auth/mikro-orm-adapter"
- "@auth/mongodb-adapter"
- "@auth/neo4j-adapter"
- "@auth/pouchdb-adapter"
- "@auth/prisma-adapter"
- "@auth/sequelize-adapter"
- "@auth/supabase-adapter"
- "@auth/typeorm-adapter"
- "@auth/upstash-redis-adapter"
- "@auth/xata-adapter"
- "next-auth"
# TODO: Infer from package name
path:
type: choice
description: Directory name (packages/*)
options:
- "core"
- "frameworks-nextjs"
- "adapter-dgraph"
- "adapter-drizzle"
- "adapter-dynamodb"
- "adapter-fauna"
- "adapter-firebase"
- "adapter-mikro-orm"
- "adapter-mongodb"
- "adapter-neo4j"
- "adapter-pouchdb"
- "adapter-prisma"
- "adapter-sequelize"
- "adapter-supabase"
- "adapter-typeorm"
- "adapter-upstash-redis"
- "adapter-xata"
- "next-auth"
env:
FORCE_COLOR: true
jobs:
test:
@@ -77,11 +27,6 @@ jobs:
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Build
run: pnpm build
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
- name: Run tests
run: pnpm test
timeout-minutes: 15
@@ -89,28 +34,7 @@ jobs:
UPSTASH_REDIS_URL: ${{ secrets.UPSTASH_REDIS_URL }}
UPSTASH_REDIS_KEY: ${{ secrets.UPSTASH_REDIS_KEY }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
- name: Upload Turbo artifacts
uses: actions/upload-artifact@v3
with:
name: turbo-report
path: .turbo/runs/
# - name: Run E2E tests
# if: github.repository == 'nextauthjs/next-auth'
# run: pnpm e2e
# timeout-minutes: 15
# env:
# AUTH0_USERNAME: ${{ secrets.AUTH0_USERNAME }}
# AUTH0_PASSWORD: ${{ secrets.AUTH0_PASSWORD }}
# TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
# TURBO_TEAM: ${{ vars.TURBO_TEAM }}
# - name: Upload E2E artifacts
# if: github.repository == 'nextauthjs/next-auth'
# uses: actions/upload-artifact@v3
# with:
# name: playwright-report
# path: apps/dev/nextjs/playwright-report/
# retention-days: 30
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
# - name: Coverage
# uses: codecov/codecov-action@v1
# with:
@@ -185,36 +109,3 @@ jobs:
env:
VERSION: ${{ steps.determine-version.outputs.version }}
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
release-manual:
name: Publish manually
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }}
steps:
- name: Init
uses: actions/checkout@v3
- name: Install pnpm
uses: pnpm/action-setup@v2.2.4
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: 18
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Determine version
uses: ./.github/version-pr
id: determine-version
env:
PACKAGE_PATH: ${{ github.event.inputs.path }}
- name: Publish to npm
run: |
pnpm build
cd packages/$PACKAGE_PATH
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> .npmrc
pnpm publish --no-git-checks --access public --tag experimental
echo "🎉 Experimental release published 📦️ on npm: https://npmjs.com/package/${{ github.event.inputs.name }}/v/${{ env.VERSION }}"
echo "Install via: pnpm add ${{ github.event.inputs.name }}@${{ env.VERSION }}"
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
PACKAGE_PATH: ${{ github.event.inputs.path }}
VERSION: ${{ steps.determine-version.outputs.version }}

33
.gitignore vendored
View File

@@ -1,21 +1,19 @@
# Misc
.DS_Store
.npmrc
.eslintcache
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
packages/*/.npmrc
npm-debug.log*
yarn-debug.log*
yarn-error.log*
firebase-debug.log
ui-debug.log
.pnpm-debug.log
.husky
# Dependencies
node_modules
@@ -36,17 +34,18 @@ packages/next-auth/utils
packages/next-auth/core
packages/next-auth/jwt
packages/next-auth/react
packages/next-auth/adapters.d.ts
packages/next-auth/adapters.js
packages/next-auth/index.d.ts
packages/next-auth/index.js
packages/next-auth/next
packages/*/*.js
packages/*/*.d.ts
packages/*/*.d.ts.map
packages/*/lib
packages/next-auth/middleware.d.ts
packages/next-auth/middleware.js
# Development app
apps/dev/src/css
apps/dev/prisma/migrations
apps/dev/typeorm
apps/dev/nextjs-2
# VS
/.vs/slnx.sqlite-journal
@@ -67,7 +66,6 @@ packages/adapter-prisma/prisma/dev.db
packages/adapter-prisma/prisma/migrations
db.sqlite
packages/adapter-supabase/supabase/.branches
packages/adapter-drizzle/.drizzle
# Tests
coverage
@@ -83,12 +81,15 @@ docs/.docusaurus
docs/providers.json
# Core
packages/core/src/providers/oauth-types.ts
packages/core/*.js
packages/core/*.d.ts
packages/core/*.d.ts.map
packages/core/lib
packages/core/providers
packages/core/src/lib/pages/styles.ts
docs/docs/reference/core
docs/docs/reference/sveltekit
docs/docs/reference/03-core
docs/docs/reference/04-sveltekit
docs-nextra/pages/reference/core
# SvelteKit
@@ -98,9 +99,3 @@ packages/frameworks-sveltekit/.svelte-kit
packages/frameworks-sveltekit/package
packages/frameworks-sveltekit/vite.config.js.timestamp-*
packages/frameworks-sveltekit/vite.config.ts.timestamp-*
# Adapters
docs/docs/reference/adapter
## Drizzle migration folder
.drizzle

1
.npmrc
View File

@@ -1 +0,0 @@
public-hoist-pattern[]=*prisma*

View File

@@ -20,8 +20,8 @@ pnpm-lock.yaml
.docusaurus
build
docs/docs/reference/core
docs/docs/reference/sveltekit
docs/docs/reference/03-core
docs/docs/reference/04-sveltekit
static
docs/providers.json

22
.prettierrc.js Normal file
View File

@@ -0,0 +1,22 @@
// @ts-check
/** @type {import("prettier").Config} */
module.exports = {
semi: false,
singleQuote: false,
overrides: [
{
files: [
"apps/dev/nextjs/pages/api/auth/[...nextauth].ts",
"docs/{sidebars,docusaurus.config}.js",
],
options: { printWidth: 150 },
},
{
files: ["**/*package.json"],
options: {
trailingComma: "none",
},
},
],
}

View File

@@ -1,61 +0,0 @@
# Rename file to .env.local (or .env) and populate values
# to be able to run the dev app
NEXTAUTH_URL=http://localhost:3000
# You can use `openssl rand -hex 32` or
# https://generate-secret.vercel.app/32 to generate a secret.
# Note: Changing a secret may invalidate existing sessions
# and/or verification tokens.
NEXTAUTH_SECRET=secret
AUTH0_ID=
AUTH0_SECRET=
AUTH0_ISSUER=
DESCOPE_ID=
DESCOPE_SECRET=
KEYCLOAK_ID=
KEYCLOAK_SECRET=
KEYCLOAK_ISSUER=
IDS4_ID=
IDS4_SECRET=
IDS4_ISSUER=
GITHUB_ID=
GITHUB_SECRET=
TWITCH_ID=
TWITCH_SECRET=
TWITTER_ID=
TWITTER_SECRET=
LINE_ID=
LINE_SECRET=
TRAKT_ID=
TRAKT_SECRET=
# Example configuration for a Gmail account (will need SMTP enabled)
EMAIL_SERVER=smtps://user@gmail.com:password@smtp.gmail.com:465
EMAIL_FROM=user@gmail.com
# Note: If using with Prisma adapter, you need to use a `.env`
# file rather than a `.env.local` file to configure env vars.
# Postgres: DATABASE_URL=postgres://nextauth:password@127.0.0.1:5432/nextauth?synchronize=true
# MySQL: DATABASE_URL=mysql://nextauth:password@127.0.0.1:3306/nextauth?synchronize=true
# MongoDB: DATABASE_URL=mongodb://nextauth:password@127.0.0.1:27017/nextauth?synchronize=true
DATABASE_URL=
WIKIMEDIA_ID=
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

View File

@@ -1,4 +0,0 @@
{
"typescript.tsdk": "../../node_modules/.pnpm/typescript@4.8.4/node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}

View File

@@ -1,6 +0,0 @@
# NextAuth.js Development App
This folder contains a Next.js app using NextAuth.js for local development. See the following section on how to start:
[Setting up local environment
](https://github.com/nextauthjs/.github/blob/main/CONTRIBUTING.md#setting-up-local-environment)

View File

@@ -1,14 +0,0 @@
import NextAuth, { type NextAuthOptions } from "next-auth"
import GitHub from "next-auth/providers/github"
export const authOptions: NextAuthOptions = {
providers: [
GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
],
}
const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }

View File

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

View File

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

View File

@@ -1,20 +0,0 @@
import { signIn } from "next-auth/react"
export default function AccessDenied() {
return (
<>
<h1>Access Denied</h1>
<p>
<a
href="/api/auth/signin"
onClick={(e) => {
e.preventDefault()
signIn()
}}
>
You must be signed in to view this page
</a>
</p>
</>
)
}

View File

@@ -1,28 +0,0 @@
import Link from "next/link"
import styles from "./footer.module.css"
import packageJSON from "package.json"
export default function Footer() {
return (
<footer className={styles.footer}>
<hr />
<ul className={styles.navItems}>
<li className={styles.navItem}>
<a href="https://next-auth.js.org">Documentation</a>
</li>
<li className={styles.navItem}>
<a href="https://www.npmjs.com/package/next-auth">NPM</a>
</li>
<li className={styles.navItem}>
<a href="https://github.com/nextauthjs/next-auth-example">GitHub</a>
</li>
<li className={styles.navItem}>
<Link href="/policy">Policy</Link>
</li>
<li className={styles.navItem}>
<em>{packageJSON.version}</em>
</li>
</ul>
</footer>
)
}

View File

@@ -1,14 +0,0 @@
.footer {
margin-top: 2rem;
}
.navItems {
margin-bottom: 1rem;
padding: 0;
list-style: none;
}
.navItem {
display: inline-block;
margin-right: 1rem;
}

View File

@@ -1,103 +0,0 @@
import Link from "next/link"
import { signIn, signOut, useSession } from "next-auth/react"
import styles from "./header.module.css"
// The approach used in this component shows how to built a sign in and sign out
// component that works on pages which support both client and server side
// rendering, and avoids any flash incorrect content on initial page load.
export default function Header() {
const { data: session, status } = useSession()
return (
<header>
<noscript>
<style>{".nojs-show { opacity: 1; top: 0; }"}</style>
</noscript>
<div className={styles.signedInStatus}>
<p
className={`nojs-show ${
!session && status === "loading" ? styles.loading : styles.loaded
}`}
>
{!session && (
<>
<span className={styles.notSignedInText}>
You are not signed in
</span>
<a
href="/api/auth/signin"
className={styles.buttonPrimary}
onClick={(e) => {
e.preventDefault()
signIn()
}}
>
Sign in
</a>
</>
)}
{session && (
<>
{session.user.image && (
<img src={session.user.image} className={styles.avatar} />
)}
<span className={styles.signedInText}>
<small>Signed in as</small>
<br />
<strong>{session.user.email} </strong>
{session.user.name ? `(${session.user.name})` : null}
</span>
<a
href="/api/auth/signout"
className={styles.button}
onClick={(e) => {
e.preventDefault()
signOut()
}}
>
Sign out
</a>
</>
)}
</p>
</div>
<nav>
<ul className={styles.navItems}>
<li className={styles.navItem}>
<Link href="/">Home</Link>
</li>
<li className={styles.navItem}>
<Link href="/client">Client</Link>
</li>
<li className={styles.navItem}>
<Link href="/server">Server</Link>
</li>
<li className={styles.navItem}>
<Link href="/protected">Protected</Link>
</li>
<li className={styles.navItem}>
<Link href="/protected-ssr">Protected(SSR)</Link>
</li>
<li className={styles.navItem}>
<Link href="/api-example">API</Link>
</li>
<li className={styles.navItem}>
<Link href="/credentials">Credentials</Link>
</li>
<li className={styles.navItem}>
<Link href="/email">Email</Link>
</li>
<li className={styles.navItem}>
<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>
</header>
)
}

View File

@@ -1,92 +0,0 @@
/* Set min-height to avoid page reflow while session loading */
.signedInStatus {
display: block;
min-height: 4rem;
width: 100%;
}
.loading,
.loaded {
position: relative;
top: 0;
opacity: 1;
overflow: hidden;
border-radius: 0 0 .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;
}

View File

@@ -1,14 +0,0 @@
import Header from 'components/header'
import Footer from 'components/footer'
export default function Layout ({ children }) {
return (
<>
<Header />
<main>
{children}
</main>
<Footer />
</>
)
}

View File

@@ -1,45 +0,0 @@
export { default } from "next-auth/middleware"
export const config = { matcher: ["/middleware-protected"] }
// Other ways to use this middleware
// import withAuth from "next-auth/middleware"
// import { withAuth } from "next-auth/middleware"
// export function middleware(req, ev) {
// return withAuth(req)
// }
// export function middleware(req, ev) {
// return withAuth(req, ev)
// }
// export function middleware(req, ev) {
// return withAuth(req, {
// callbacks: {
// authorized: ({ token }) => !!token,
// },
// })
// }
// export default withAuth(function middleware(req, ev) {
// console.log(req.nextauth.token)
// })
// export default withAuth(
// function middleware(req, ev) {
// console.log(req, ev)
// },
// {
// callbacks: {
// authorized: ({ token }) => token.name === "Balázs Orbán",
// },
// }
// )
// export default withAuth({
// callbacks: {
// authorized: ({ token }) => !!token,
// },
// })

View File

@@ -1,6 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -1,9 +0,0 @@
/** @type {import("next").NextConfig} */
module.exports = {
webpack(config) {
config.experiments = { ...config.experiments, topLevelAwait: true }
return config
},
experimental: { appDir: true },
typescript: { ignoreBuildErrors: true },
}

View File

@@ -1,40 +0,0 @@
{
"name": "next-auth-app-v4",
"version": "1.0.0",
"description": "NextAuth.js Developer app",
"private": true,
"scripts": {
"clean": "rm -rf .next",
"dev": "next dev",
"lint": "next lint",
"build": "next build",
"start": "next start",
"email": "fake-smtp-server",
"start:email": "pnpm email"
},
"license": "ISC",
"dependencies": {
"@auth/fauna-adapter": "workspace:*",
"@auth/prisma-adapter": "workspace:*",
"@auth/supabase-adapter": "workspace:*",
"@auth/typeorm-adapter": "workspace:*",
"@prisma/client": "^3",
"@supabase/supabase-js": "^2.0.5",
"faunadb": "^4",
"next": "13.3.0",
"next-auth": "workspace:*",
"nodemailer": "^6",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@types/jsonwebtoken": "^8.5.5",
"@types/react": "^18.0.37",
"@types/react-dom": "^18.0.6",
"fake-smtp-server": "^0.8.0",
"pg": "^8.7.3",
"prisma": "^3",
"sqlite3": "^5.0.8",
"typeorm": "0.3.7"
}
}

View File

@@ -1,10 +0,0 @@
import { SessionProvider } from "next-auth/react"
import "./styles.css"
export default function App({ Component, pageProps }) {
return (
<SessionProvider session={pageProps.session}>
<Component {...pageProps} />
</SessionProvider>
)
}

View File

@@ -1,17 +0,0 @@
import Layout from '../components/layout'
export default function Page () {
return (
<Layout>
<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' />
</Layout>
)
}

View File

@@ -1,217 +0,0 @@
import NextAuth, { NextAuthOptions } from "next-auth"
// Providers
import Apple from "next-auth/providers/apple"
import Auth0 from "next-auth/providers/auth0"
import AzureAD from "next-auth/providers/azure-ad"
import AzureB2C from "next-auth/providers/azure-ad-b2c"
import BoxyHQSAML from "next-auth/providers/boxyhq-saml"
// import Cognito from "next-auth/providers/cognito"
import Credentials from "next-auth/providers/credentials"
import Discord from "next-auth/providers/discord"
import DuendeIDS6 from "next-auth/providers/duende-identity-server6"
// import Email from "next-auth/providers/email"
import Facebook from "next-auth/providers/facebook"
import Foursquare from "next-auth/providers/foursquare"
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 IDS4 from "next-auth/providers/identity-server4"
import Instagram from "next-auth/providers/instagram"
// import Keycloak from "next-auth/providers/keycloak"
import Line from "next-auth/providers/line"
import LinkedIn from "next-auth/providers/linkedin"
import Mailchimp from "next-auth/providers/mailchimp"
// import Okta from "next-auth/providers/okta"
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 Trakt from "next-auth/providers/trakt"
import Twitch from "next-auth/providers/twitch"
import Twitter 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"
// // Prisma
// import { PrismaClient } from "@prisma/client"
// import { PrismaAdapter } from "@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 "@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 { TypeORMAdapter } from "@auth/typeorm-adapter"
// const adapter = TypeORMAdapter({
// type: "sqlite",
// name: "next-auth-test-memory",
// database: "./typeorm/dev.db",
// synchronize: true,
// })
// // Supabase
// import { SupabaseAdapter } from "@auth/supabase-adapter"
// const adapter = SupabaseAdapter({
// url: process.env.NEXT_PUBLIC_SUPABASE_URL,
// secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
// })
export const authOptions: NextAuthOptions = {
// adapter,
// debug: process.env.NODE_ENV !== "production",
theme: {
logo: "https://next-auth.js.org/img/logo/logo-sm.png",
brandColor: "#1786fb",
},
providers: [
Credentials({
credentials: { password: { label: "Password", type: "password" } },
async authorize(credentials) {
if (credentials.password !== "pw") return null
return {
name: "Fill Murray",
email: "bill@fillmurray.com",
image: "https://www.fillmurray.com/64/64",
id: "1",
foo: "",
}
},
}),
Apple({
clientId: process.env.APPLE_ID,
clientSecret: process.env.APPLE_SECRET,
}),
Auth0({
clientId: process.env.AUTH0_ID,
clientSecret: process.env.AUTH0_SECRET,
issuer: process.env.AUTH0_ISSUER,
}),
AzureAD({
clientId: process.env.AZURE_AD_CLIENT_ID,
clientSecret: process.env.AZURE_AD_CLIENT_SECRET,
tenantId: process.env.AZURE_AD_TENANT_ID,
}),
AzureB2C({
clientId: process.env.AZURE_B2C_ID,
clientSecret: process.env.AZURE_B2C_SECRET,
issuer: process.env.AZURE_B2C_ISSUER,
}),
BoxyHQSAML({
issuer: "https://jackson-demo.boxyhq.com",
clientId: "tenant=boxyhq.com&product=saml-demo.boxyhq.com",
clientSecret: "dummy",
}),
// Cognito({ clientId: process.env.COGNITO_ID, clientSecret: process.env.COGNITO_SECRET, issuer: process.env.COGNITO_ISSUER }),
Discord({
clientId: process.env.DISCORD_ID,
clientSecret: process.env.DISCORD_SECRET,
}),
DuendeIDS6({
clientId: "interactive.confidential",
clientSecret: "secret",
issuer: "https://demo.duendesoftware.com",
}),
Facebook({
clientId: process.env.FACEBOOK_ID,
clientSecret: process.env.FACEBOOK_SECRET,
}),
Foursquare({
clientId: process.env.FOURSQUARE_ID,
clientSecret: process.env.FOURSQUARE_SECRET,
}),
Freshbooks({
clientId: process.env.FRESHBOOKS_ID,
clientSecret: process.env.FRESHBOOKS_SECRET,
}),
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,
}),
// 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 }),
Line({
clientId: process.env.LINE_ID,
clientSecret: process.env.LINE_SECRET,
}),
LinkedIn({
clientId: process.env.LINKEDIN_ID,
clientSecret: process.env.LINKEDIN_SECRET,
}),
Mailchimp({
clientId: process.env.MAILCHIMP_ID,
clientSecret: process.env.MAILCHIMP_SECRET,
}),
// Okta({ clientId: process.env.OKTA_ID, clientSecret: process.env.OKTA_SECRET, issuer: process.env.OKTA_ISSUER }),
Osu({
clientId: process.env.OSU_CLIENT_ID,
clientSecret: process.env.OSU_CLIENT_SECRET,
}),
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,
}),
Trakt({
clientId: process.env.TRAKT_ID,
clientSecret: process.env.TRAKT_SECRET,
}),
Twitch({
clientId: process.env.TWITCH_ID,
clientSecret: process.env.TWITCH_SECRET,
}),
Twitter({
clientId: process.env.TWITTER_ID,
clientSecret: process.env.TWITTER_SECRET,
}),
// TwitterLegacy({ clientId: process.env.TWITTER_LEGACY_ID, clientSecret: process.env.TWITTER_LEGACY_SECRET }),
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,
}),
],
}
if (authOptions.adapter) {
// TODO:
// authOptions.providers.unshift(
// // NOTE: You can start a fake e-mail server with `pnpm email`
// // and then go to `http://localhost:1080` in the browser
// Email({ server: "smtp://127.0.0.1:1025?tls.rejectUnauthorized=false" })
// )
}
export default NextAuth(authOptions)

View File

@@ -1,7 +0,0 @@
// This is an example of how to read a JSON Web Token from an API route
import { getToken } from "next-auth/jwt"
export default async (req, res) => {
const token = await getToken({ req })
res.send(JSON.stringify(token, null, 2))
}

View File

@@ -1,19 +0,0 @@
// This is an example of to protect an API route
import { getServerSession } from "next-auth/next"
import { authOptions } from "../auth/[...nextauth]"
export default async (req, res) => {
const session = await getServerSession(req, res, authOptions)
if (session) {
res.send({
content:
"This is protected content. You can access this content because you are signed in.",
session,
})
} else {
res.send({
error: "You must be sign in to view the protected content on this page.",
})
}
}

View File

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

View File

@@ -1,30 +0,0 @@
// 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 { getServerSession } from "next-auth/next"
import { authOptions } from "../auth/[...nextauth]"
import { createClient } from "@supabase/supabase-js"
export default async (req, res) => {
const session = await 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

@@ -1,22 +0,0 @@
import Layout from '../components/layout'
export default function Page () {
return (
<Layout>
<h1>Client Side Rendering</h1>
<p>
This page uses the <strong>useSession()</strong> React Hook in the <strong>&lt;/Header&gt;</strong> component.
</p>
<p>
The <strong>useSession()</strong> React Hook 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 the <strong>Provider</strong> in <strong>_app.js</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>
</Layout>
)
}

View File

@@ -1,67 +0,0 @@
// eslint-disable-next-line no-use-before-define
import * as React from "react"
import { signIn, signOut, useSession } from "next-auth/react"
import Layout from "components/layout"
export default function Page() {
const [response, setResponse] = React.useState(null)
const handleLogin = (options) => async () => {
if (options.redirect) {
return signIn("credentials", options)
}
const response = await signIn("credentials", options)
setResponse(response)
}
const handleLogout = (options) => async () => {
if (options.redirect) {
return signOut(options)
}
const response = await signOut(options)
setResponse(response)
}
const { data: session } = useSession()
if (session) {
return (
<Layout>
<h1>Test different flows for Credentials logout</h1>
<span className="spacing">Default:</span>
<button onClick={handleLogout({ redirect: true })}>Logout</button>
<br />
<span className="spacing">No redirect:</span>
<button onClick={handleLogout({ redirect: false })}>Logout</button>
<br />
<p>Response:</p>
<pre style={{ background: "#eee", padding: 16 }}>
{JSON.stringify(response, null, 2)}
</pre>
</Layout>
)
}
return (
<Layout>
<h1>Test different flows for Credentials login</h1>
<span className="spacing">Default:</span>
<button onClick={handleLogin({ redirect: true, password: "password" })}>
Login
</button>
<br />
<span className="spacing">No redirect:</span>
<button onClick={handleLogin({ redirect: false, password: "password" })}>
Login
</button>
<br />
<span className="spacing">No redirect, wrong password:</span>
<button onClick={handleLogin({ redirect: false, password: "" })}>
Login
</button>
<p>Response:</p>
<pre style={{ background: "#eee", padding: 16 }}>
{JSON.stringify(response, null, 2)}
</pre>
</Layout>
)
}

View File

@@ -1,80 +0,0 @@
// eslint-disable-next-line no-use-before-define
import * as React from "react"
import { signIn, signOut, useSession } from "next-auth/react"
import Layout from "components/layout"
export default function Page() {
const [response, setResponse] = React.useState(null)
const [email, setEmail] = React.useState("")
const handleChange = (event) => {
setEmail(event.target.value)
}
const handleLogin = (options) => async (event) => {
event.preventDefault()
if (options.redirect) {
return signIn("email", options)
}
const response = await signIn("email", options)
setResponse(response)
}
const handleLogout = (options) => async (event) => {
if (options.redirect) {
return signOut(options)
}
const response = await signOut(options)
setResponse(response)
}
const { data: session } = useSession()
if (session) {
return (
<Layout>
<h1>Test different flows for Email logout</h1>
<span className="spacing">Default:</span>
<button onClick={handleLogout({ redirect: true })}>Logout</button>
<br />
<span className="spacing">No redirect:</span>
<button onClick={handleLogout({ redirect: false })}>Logout</button>
<br />
<p>Response:</p>
<pre style={{ background: "#eee", padding: 16 }}>
{JSON.stringify(response, null, 2)}
</pre>
</Layout>
)
}
return (
<Layout>
<h1>Test different flows for Email login</h1>
<label className="spacing">
Email address:{" "}
<input
type="text"
id="email"
name="email"
value={email}
onChange={handleChange}
/>
</label>
<br />
<form onSubmit={handleLogin({ redirect: true, email })}>
<span className="spacing">Default:</span>
<button type="submit">Sign in with Email</button>
</form>
<form onSubmit={handleLogin({ redirect: false, email })}>
<span className="spacing">No redirect:</span>
<button type="submit">Sign in with Email</button>
</form>
<p>Response:</p>
<pre style={{ background: "#eee", padding: 16 }}>
{JSON.stringify(response, null, 2)}
</pre>
</Layout>
)
}

View File

@@ -1,12 +0,0 @@
import Layout from 'components/layout'
export default function Page () {
return (
<Layout>
<h1>NextAuth.js Example</h1>
<p>
This is an example site to demonstrate how to use <a href='https://next-auth.js.org'>NextAuth.js</a> for authentication.
</p>
</Layout>
)
}

View File

@@ -1,9 +0,0 @@
import Layout from "components/layout"
export default function Page() {
return (
<Layout>
<h1>Page protected by Middleware</h1>
</Layout>
)
}

View File

@@ -1,30 +0,0 @@
import Layout from '../components/layout'
export default function Page () {
return (
<Layout>
<p>
This is an example site to demonstrate how to use <a href='https://next-auth.js.org'>NextAuth.js</a> for authentication.
</p>
<h2>Terms of Service</h2>
<p>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</p>
<h2>Privacy Policy</h2>
<p>
This site uses JSON Web Tokens and an in-memory database which resets every ~2 hours.
</p>
<p>
Data provided to this site is exclusively used to support signing in
and is not passed to any third party services, other than via SMTP or OAuth for the
purposes of authentication.
</p>
</Layout>
)
}

View File

@@ -1,48 +0,0 @@
// This is an example of how to protect content using server rendering
import { getServerSession } from "next-auth/next"
import { authOptions } from "./api/auth/[...nextauth]"
import Layout from "../components/layout"
import AccessDenied from "../components/access-denied"
export default function Page({ content, 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>
<strong>{content}</strong>
</p>
</Layout>
)
}
export async function getServerSideProps(context) {
const session = await getServerSession(context.req, context.res, authOptions)
let content = null
if (session) {
const hostname = process.env.NEXTAUTH_URL || "http://localhost:3000"
const options = { headers: { cookie: context.req.headers.cookie } }
const res = await fetch(`${hostname}/api/examples/protected`, options)
const json = await res.json()
if (json.content) {
content = json.content
}
}
return {
props: {
session,
content,
},
}
}

View File

@@ -1,35 +0,0 @@
import { useState, useEffect } from "react"
import { useSession } from "next-auth/react"
import Layout from "../components/layout"
export default function Page() {
const { status } = useSession({
required: true,
})
const [content, setContent] = useState()
// Fetch content from protected route
useEffect(() => {
if (status === "loading") return
const fetchData = async () => {
const res = await fetch("/api/examples/protected")
const json = await res.json()
if (json.content) {
setContent(json.content)
}
}
fetchData()
}, [status])
if (status === "loading") return <Layout>Loading...</Layout>
// If session exists, display content
return (
<Layout>
<h1>Protected Page</h1>
<p>
<strong>{content}</strong>
</p>
</Layout>
)
}

View File

@@ -1,46 +0,0 @@
import { getServerSession } from "next-auth/next"
import Layout from "../components/layout"
import { authOptions } from "./api/auth/[...nextauth]"
export default function Page() {
// As this page uses Server Side Rendering, the `session` will be already
// populated on render without needing to go through a loading stage.
// This is possible because of the shared context configured in `_app.js` that
// is used by `useSession()`.
return (
<Layout>
<h1>Server Side Rendering</h1>
<p>
This page uses the <strong>getServerSession()</strong> method in{" "}
<strong>getServerSideProps()</strong>.
</p>
<p>
Using <strong>getServerSession()</strong> in{" "}
<strong>getServerSideProps()</strong> is currently the recommended
approach, although the API may still change, if you need to support
Server Side Rendering with authentication.
</p>
<p>
Using <strong>getSession()</strong> is still recommended on the client.
</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>
</Layout>
)
}
// Export the `session` prop to use sessions with Server Side Rendering
export async function getServerSideProps(context) {
return {
props: {
session: await getServerSession(context.req, context.res, authOptions),
},
}
}

View File

@@ -1,32 +0,0 @@
body {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
padding: 0 1rem 1rem 1rem;
max-width: 680px;
margin: 0 auto;
background: #fff;
color: var(--color-text);
}
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);
}

View File

@@ -1,49 +0,0 @@
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

@@ -1,64 +0,0 @@
// This is an example of how to protect content using server rendering
// and fetching data from Supabase with RLS enabled.
import { 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 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,60 +0,0 @@
-- CreateTable
CREATE TABLE "Account" (
"id" TEXT NOT NULL PRIMARY KEY,
"userId" TEXT NOT NULL,
"type" TEXT NOT NULL,
"provider" TEXT NOT NULL,
"providerAccountId" TEXT NOT NULL,
"refresh_token" TEXT,
"access_token" TEXT,
"expires_at" INTEGER,
"token_type" TEXT,
"scope" TEXT,
"id_token" TEXT,
"session_state" TEXT,
"oauth_token_secret" TEXT,
"oauth_token" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "Session" (
"id" TEXT NOT NULL PRIMARY KEY,
"sessionToken" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"expires" DATETIME NOT NULL,
CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT,
"email" TEXT,
"emailVerified" DATETIME,
"image" TEXT
);
-- CreateTable
CREATE TABLE "VerificationToken" (
"identifier" TEXT NOT NULL,
"token" TEXT NOT NULL,
"expires" DATETIME NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
-- CreateIndex
CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token");
-- CreateIndex
CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");

View File

@@ -1,3 +0,0 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "sqlite"

View File

@@ -1,57 +0,0 @@
datasource db {
provider = "sqlite"
url = "file:./dev.db"
}
generator client {
provider = "prisma-client-js"
}
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String?
access_token String?
expires_at Int?
token_type String?
scope String?
id_token String?
session_state String?
oauth_token_secret String?
oauth_token String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
@@unique([provider, providerAccountId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id])
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
}

View File

@@ -1,39 +0,0 @@
{
"compilerOptions": {
"target": "esnext",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"incremental": true,
"jsx": "preserve",
"baseUrl": ".",
"plugins": [
{
"name": "next"
}
],
"strictNullChecks": true
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules",
"jest.config.js"
]
}

View File

@@ -1,20 +0,0 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import NextAuth 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
} & User
}
interface User {
foo: string
}
}

View File

@@ -9,42 +9,20 @@ NEXTAUTH_URL=http://localhost:3000
# and/or verification tokens.
NEXTAUTH_SECRET=secret
ASGARDEO_CLIENT_ID=
ASGARDEO_CLIENT_SECRET=
ASGARDEO_ISSUER=
AUTH0_ID=
AUTH0_SECRET=
AUTH0_ISSUER=
# Beyond Identity Provider
BEYOND_IDENTITY_CLIENT_ID=
BEYOND_IDENTITY_CLIENT_SECRET=
BEYOND_IDENTITY_ISSUER=
DESCOPE_ID=
DESCOPE_SECRET=
GITHUB_ID=
GITHUB_SECRET=
NOTION_ID=
NOTION_SECRET=
NOTION_REDIRECT_URI=
IDS4_ID=
IDS4_SECRET=
IDS4_ISSUER=
KEYCLOAK_ID=
KEYCLOAK_SECRET=
KEYCLOAK_ISSUER=
LINE_ID=
LINE_SECRET=
IDS4_ID=
IDS4_SECRET=
IDS4_ISSUER=
TRAKT_ID=
TRAKT_SECRET=
GITHUB_ID=
GITHUB_SECRET=
TWITCH_ID=
TWITCH_SECRET=
@@ -52,12 +30,11 @@ TWITCH_SECRET=
TWITTER_ID=
TWITTER_SECRET=
WIKIMEDIA_ID=
WIKIMEDIA_SECRET=
LINE_ID=
LINE_SECRET=
# Yandex OAuth. new app -> https://oauth.yandex.com/client/new/id
YANDEX_ID=
YANDEX_SECRET=
TRAKT_ID=
TRAKT_SECRET=
# Example configuration for a Gmail account (will need SMTP enabled)
EMAIL_SERVER=smtps://user@gmail.com:password@smtp.gmail.com:465
@@ -70,9 +47,12 @@ EMAIL_FROM=user@gmail.com
# MongoDB: DATABASE_URL=mongodb://nextauth:password@127.0.0.1:27017/nextauth?synchronize=true
DATABASE_URL=
WIKIMEDIA_ID=
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
# NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs

View File

@@ -1,4 +0,0 @@
node_modules/
/test-results/
/playwright-report/
/playwright/.cache/

View File

@@ -1,6 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -9,31 +9,28 @@
"build": "next build",
"start": "next start",
"email": "fake-smtp-server",
"start:email": "pnpm email",
"e2e": "pnpm dlx playwright test"
"start:email": "pnpm email"
},
"license": "ISC",
"dependencies": {
"@auth/core": "workspace:*",
"@auth/fauna-adapter": "workspace:*",
"@auth/prisma-adapter": "workspace:*",
"@auth/supabase-adapter": "workspace:*",
"@auth/typeorm-adapter": "workspace:*",
"@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": "13.4.0",
"next": "13.1.1",
"next-auth": "workspace:*",
"@auth/core": "workspace:*",
"nodemailer": "^6",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@playwright/test": "1.29.2",
"@types/jsonwebtoken": "^8.5.5",
"@types/react": "18.0.37",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"dotenv": "^16.0.3",
"fake-smtp-server": "^0.8.0",
"pg": "^8.7.3",
"prisma": "^3",

View File

@@ -2,15 +2,12 @@ import { Auth, type AuthConfig } from "@auth/core"
// Providers
import Apple from "@auth/core/providers/apple"
// import Asgardeo from "@auth/core/providers/asgardeo"
import Auth0 from "@auth/core/providers/auth0"
import AzureAD from "@auth/core/providers/azure-ad"
import AzureB2C from "@auth/core/providers/azure-ad-b2c"
// import BeyondIdentity from "@auth/core/providers/beyondidentity"
import BoxyHQSAML from "@auth/core/providers/boxyhq-saml"
// import Cognito from "@auth/core/providers/cognito"
import Credentials from "@auth/core/providers/credentials"
import Descope from "@auth/core/providers/descope"
import Discord from "@auth/core/providers/discord"
import DuendeIDS6 from "@auth/core/providers/duende-identity-server6"
// import Email from "@auth/core/providers/email"
@@ -26,7 +23,6 @@ import Instagram from "@auth/core/providers/instagram"
import Line from "@auth/core/providers/line"
import LinkedIn from "@auth/core/providers/linkedin"
import Mailchimp from "@auth/core/providers/mailchimp"
import Notion from "@auth/core/providers/notion"
// import Okta from "@auth/core/providers/okta"
import Osu from "@auth/core/providers/osu"
import Patreon from "@auth/core/providers/patreon"
@@ -35,29 +31,28 @@ import Spotify from "@auth/core/providers/spotify"
import Trakt from "@auth/core/providers/trakt"
import Twitch from "@auth/core/providers/twitch"
import Twitter from "@auth/core/providers/twitter"
import Yandex from "@auth/core/providers/yandex"
import Vk from "@auth/core/providers/vk"
import Wikimedia from "@auth/core/providers/wikimedia"
import WorkOS from "@auth/core/providers/workos"
// // Prisma
// import { PrismaClient } from "@prisma/client"
// import { PrismaAdapter } from "@auth/prisma-adapter"
// 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 "@auth/fauna-adapter"
// 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 { TypeORMAdapter } from "@auth/typeorm-adapter"
// const adapter = TypeORMAdapter({
// import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter"
// const adapter = TypeORMLegacyAdapter({
// type: "sqlite",
// name: "next-auth-test-memory",
// database: "./typeorm/dev.db",
@@ -65,7 +60,7 @@ import WorkOS from "@auth/core/providers/workos"
// })
// // Supabase
// import { SupabaseAdapter } from "@auth/supabase-adapter"
// import { SupabaseAdapter } from "@next-auth/supabase-adapter"
// const adapter = SupabaseAdapter({
// url: process.env.NEXT_PUBLIC_SUPABASE_URL,
// secret: process.env.SUPABASE_SERVICE_ROLE_KEY,
@@ -73,7 +68,7 @@ import WorkOS from "@auth/core/providers/workos"
export const authConfig: AuthConfig = {
// adapter,
debug: process.env.NODE_ENV !== "production",
// debug: process.env.NODE_ENV !== "production",
theme: {
logo: "https://next-auth.js.org/img/logo/logo-sm.png",
brandColor: "#1786fb",
@@ -86,8 +81,7 @@ export const authConfig: AuthConfig = {
return { name: "Fill Murray", email: "bill@fillmurray.com", image: "https://www.fillmurray.com/64/64", id: "1", foo: "" }
},
}),
Apple({ clientId: process.env.APPLE_ID, clientSecret: process.env.APPLE_SECRET as string }),
// Asgardeo({ clientId: process.env.ASGARDEO_CLIENT_ID, clientSecret: process.env.ASGARDEO_CLIENT_SECRET, issuer: process.env.ASGARDEO_ISSUER }),
Apple({ clientId: process.env.APPLE_ID, clientSecret: process.env.APPLE_SECRET }),
Auth0({ clientId: process.env.AUTH0_ID, clientSecret: process.env.AUTH0_SECRET, issuer: process.env.AUTH0_ISSUER }),
AzureAD({
clientId: process.env.AZURE_AD_CLIENT_ID,
@@ -95,20 +89,14 @@ export const authConfig: AuthConfig = {
tenantId: process.env.AZURE_AD_TENANT_ID,
}),
AzureB2C({ clientId: process.env.AZURE_B2C_ID, clientSecret: process.env.AZURE_B2C_SECRET, issuer: process.env.AZURE_B2C_ISSUER }),
// BeyondIdentity({
// clientId: process.env.BEYOND_IDENTITY_CLIENT_ID,
// clientSecret: process.env.BEYOND_IDENTITY_CLIENT_SECRET,
// issuer: process.env.BEYOND_IDENTITY_ISSUER,
// }),
BoxyHQSAML({ issuer: "https://jackson-demo.boxyhq.com", clientId: "tenant=boxyhq.com&product=saml-demo.boxyhq.com", clientSecret: "dummy" }),
// Cognito({ clientId: process.env.COGNITO_ID, clientSecret: process.env.COGNITO_SECRET, issuer: process.env.COGNITO_ISSUER }),
Descope({ clientId: process.env.DESCOPE_ID, clientSecret: process.env.DESCOPE_SECRET }),
Discord({ clientId: process.env.DISCORD_ID, clientSecret: process.env.DISCORD_SECRET }),
DuendeIDS6({ clientId: "interactive.confidential", clientSecret: "secret", issuer: "https://demo.duendesoftware.com" }),
Facebook({ clientId: process.env.FACEBOOK_ID, clientSecret: process.env.FACEBOOK_SECRET }),
Foursquare({ clientId: process.env.FOURSQUARE_ID, clientSecret: process.env.FOURSQUARE_SECRET }),
Freshbooks({ clientId: process.env.FRESHBOOKS_ID, clientSecret: process.env.FRESHBOOKS_SECRET }),
GitHub({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET, redirectProxyUrl: process.env.AUTH_REDIRECT_PROXY_URL }),
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 }),
// IDS4({ clientId: process.env.IDS4_ID, clientSecret: process.env.IDS4_SECRET, issuer: process.env.IDS4_ISSUER }),
@@ -117,7 +105,6 @@ export const authConfig: AuthConfig = {
Line({ clientId: process.env.LINE_ID, clientSecret: process.env.LINE_SECRET }),
LinkedIn({ clientId: process.env.LINKEDIN_ID, clientSecret: process.env.LINKEDIN_SECRET }),
Mailchimp({ clientId: process.env.MAILCHIMP_ID, clientSecret: process.env.MAILCHIMP_SECRET }),
Notion({ clientId: process.env.NOTION_ID, clientSecret: process.env.NOTION_SECRET, redirectUri: process.env.NOTION_REDIRECT_URI as string }),
// Okta({ clientId: process.env.OKTA_ID, clientSecret: process.env.OKTA_SECRET, issuer: process.env.OKTA_ISSUER }),
Osu({ clientId: process.env.OSU_CLIENT_ID, clientSecret: process.env.OSU_CLIENT_SECRET }),
Patreon({ clientId: process.env.PATREON_ID, clientSecret: process.env.PATREON_SECRET }),
@@ -127,7 +114,6 @@ export const authConfig: AuthConfig = {
Twitch({ clientId: process.env.TWITCH_ID, clientSecret: process.env.TWITCH_SECRET }),
Twitter({ clientId: process.env.TWITTER_ID, clientSecret: process.env.TWITTER_SECRET }),
// TwitterLegacy({ clientId: process.env.TWITTER_LEGACY_ID, clientSecret: process.env.TWITTER_LEGACY_SECRET }),
Yandex({ clientId: process.env.YANDEX_ID, clientSecret: process.env.YANDEX_SECRET }),
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 }),
@@ -162,4 +148,4 @@ function AuthHandler(...args: any[]) {
export default AuthHandler(authConfig)
export const config = { runtime: "edge" }
export const config = { runtime: "experimental-edge" }

View File

@@ -1,107 +0,0 @@
import type { PlaywrightTestConfig } from '@playwright/test';
import { devices } from '@playwright/test';
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
require('dotenv').config();
/**
* See https://playwright.dev/docs/test-configuration.
*/
const config: PlaywrightTestConfig = {
testDir: './tests',
/* Maximum time one test can run for. */
timeout: 30 * 1000,
expect: {
/**
* Maximum time expect() should wait for the condition to be met.
* For example in `await expect(locator).toHaveText();`
*/
timeout: 5000
},
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
actionTimeout: 0,
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
},
{
name: 'firefox',
use: {
...devices['Desktop Firefox'],
},
},
{
name: 'webkit',
use: {
...devices['Desktop Safari'],
},
},
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: {
// ...devices['Pixel 5'],
// },
// },
// {
// name: 'Mobile Safari',
// use: {
// ...devices['iPhone 12'],
// },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: {
// channel: 'msedge',
// },
// },
// {
// name: 'Google Chrome',
// use: {
// channel: 'chrome',
// },
// },
],
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
// outputDir: 'test-results/',
/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// port: 3000,
// },
};
export default config;

View File

@@ -1,39 +0,0 @@
import { test, expect } from "@playwright/test"
test("Sign in with Auth0", async ({ page }) => {
// Go to NextAuth example app
await page.goto("https://next-auth-example.vercel.app")
// Click 'Sign In'
await page.click("#__next > header > div > p > a")
// Auth0 Login Provider
await page.click('body > div > div form[action*="auth0"] > button')
// Enter Credentials (Username/Password Login) on Auth0 Widget
await page.type("#username", process.env.AUTH0_USERNAME!)
await page.type("#password", process.env.AUTH0_PASSWORD!)
// Snap a screenshot
// await page.screenshot({ path: "1-auth0-login.png", fullPage: true })
// Press submit on Auth0 form
await page.click('body > div > main > section > div button[type="submit"]')
// Wait for next-auth example page login status header to appear
await page.waitForTimeout(2000)
// Snap a screenshot
// await page.screenshot({
// path: "2-next-auth-redirect-result.png",
// fullPage: false,
// })
// Check session object after successful login
const response = await page.goto(
"https://next-auth-example.vercel.app/api/auth/session"
)
const session = await response?.json()
expect(session?.user?.email).toBe(process.env.AUTH0_USERNAME)
// TODO: Check whole object with .toEqual()
})

View File

@@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "esnext",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
@@ -23,17 +19,8 @@
{
"name": "next"
}
],
"strictNullChecks": true
]
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules",
"jest.config.js"
]
}
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules", "jest.config.js"]
}

View File

@@ -16,11 +16,11 @@
"svelte": "3.55.0",
"svelte-check": "2.10.2",
"typescript": "4.9.4",
"vite": "4.0.5"
"vite": "4.0.1"
},
"dependencies": {
"@auth/core": "workspace:*",
"@auth/sveltekit": "workspace:*"
"@auth/core": "0.2.5",
"@auth/sveltekit": "0.1.12"
},
"type": "module"
}

View File

@@ -1,14 +1,15 @@
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET= # Linux: `openssl rand -hex 32` or go to https://generate-secret.now.sh/32
APPLE_ID=
APPLE_TEAM_ID=
APPLE_PRIVATE_KEY=
APPLE_KEY_ID=
AUTH0_ID=
AUTH0_SECRET=
AUTH0_ISSUER=
DESCOPE_ID=
DESCOPE_SECRET=
FACEBOOK_ID=
FACEBOOK_SECRET=
@@ -20,3 +21,8 @@ GOOGLE_SECRET=
TWITTER_ID=
TWITTER_SECRET=
EMAIL_SERVER=smtp://username:password@smtp.example.com:587
EMAIL_FROM=NextAuth <noreply@example.com>
DATABASE_URL=sqlite://localhost/:memory:?synchronize=true

View File

@@ -1,4 +0,0 @@
/** @type {import("next").NextConfig} */
module.exports = {
reactStrictMode: true,
}

View File

@@ -20,12 +20,13 @@
"dependencies": {
"next": "latest",
"next-auth": "latest",
"nodemailer": "^6",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/node": "^18.16.2",
"@types/react": "^18.2.0",
"typescript": "^5.0.4"
"@types/node": "^17",
"@types/react": "^18.0.15",
"typescript": "^4"
}
}

View File

@@ -7,7 +7,7 @@ export default function Page() {
<p>Only admin users can see this page.</p>
<p>
To learn more about the NextAuth middleware see&nbsp;
<a href="https://next-auth.js.org/configuration/nextjs#middleware">
<a href="https://docs-git-misc-docs-nextauthjs.vercel.app/configuration/nextjs#middleware">
the docs
</a>
.

View File

@@ -4,17 +4,31 @@ import FacebookProvider from "next-auth/providers/facebook"
import GithubProvider from "next-auth/providers/github"
import TwitterProvider from "next-auth/providers/twitter"
import Auth0Provider from "next-auth/providers/auth0"
// import AppleProvider from "next-auth/providers/apple"
// import EmailProvider from "next-auth/providers/email"
// For more information on each option (and a full list of options) go to
// https://next-auth.js.org/configuration/options
export const authOptions: NextAuthOptions = {
// https://next-auth.js.org/configuration/providers/oauth
providers: [
Auth0Provider({
clientId: process.env.AUTH0_ID,
clientSecret: process.env.AUTH0_SECRET,
issuer: process.env.AUTH0_ISSUER,
/* EmailProvider({
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
}),
// Temporarily removing the Apple provider from the demo site as the
// callback URL for it needs updating due to Vercel changing domains
Providers.Apple({
clientId: process.env.APPLE_ID,
clientSecret: {
appleId: process.env.APPLE_ID,
teamId: process.env.APPLE_TEAM_ID,
privateKey: process.env.APPLE_PRIVATE_KEY,
keyId: process.env.APPLE_KEY_ID,
},
}),
*/
FacebookProvider({
clientId: process.env.FACEBOOK_ID,
clientSecret: process.env.FACEBOOK_SECRET,
@@ -30,9 +44,16 @@ export const authOptions: NextAuthOptions = {
TwitterProvider({
clientId: process.env.TWITTER_ID,
clientSecret: process.env.TWITTER_SECRET,
version: "2.0",
}),
Auth0Provider({
clientId: process.env.AUTH0_ID,
clientSecret: process.env.AUTH0_SECRET,
issuer: process.env.AUTH0_ISSUER,
}),
],
theme: {
colorScheme: "light",
},
callbacks: {
async jwt({ token }) {
token.userRole = "admin"

View File

@@ -1,5 +1,5 @@
// This is an example of to protect an API route
import { getServerSession } from "next-auth/next"
import { unstable_getServerSession } from "next-auth/next"
import { authOptions } from "../auth/[...nextauth]"
import type { NextApiRequest, NextApiResponse } from "next"
@@ -8,7 +8,7 @@ export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const session = await getServerSession(req, res, authOptions)
const session = await unstable_getServerSession(req, res, authOptions)
if (session) {
return res.send({

View File

@@ -1,5 +1,5 @@
// This is an example of how to access a session from an API route
import { getServerSession } from "next-auth"
import { unstable_getServerSession } from "next-auth"
import { authOptions } from "../auth/[...nextauth]"
import type { NextApiRequest, NextApiResponse } from "next"
@@ -8,6 +8,6 @@ export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const session = await getServerSession(req, res, authOptions)
const session = await unstable_getServerSession(req, res, authOptions)
res.send(JSON.stringify(session, null, 2))
}

View File

@@ -1,4 +1,4 @@
import { getServerSession } from "next-auth/next"
import { unstable_getServerSession } from "next-auth/next"
import { authOptions } from "./api/auth/[...nextauth]"
import Layout from "../components/layout"
@@ -13,11 +13,11 @@ export default function ServerSidePage() {
<Layout>
<h1>Server Side Rendering</h1>
<p>
This page uses the <strong>getServerSession()</strong> method in{" "}
<strong>getServerSideProps()</strong>.
This page uses the <strong>unstable_getServerSession()</strong> method
in <strong>getServerSideProps()</strong>.
</p>
<p>
Using <strong>getServerSession()</strong> in{" "}
Using <strong>unstable_getServerSession()</strong> in{" "}
<strong>getServerSideProps()</strong> is the recommended approach if you
need to support Server Side Rendering with authentication.
</p>
@@ -38,7 +38,11 @@ export default function ServerSidePage() {
export async function getServerSideProps(context: GetServerSidePropsContext) {
return {
props: {
session: await getServerSession(context.req, context.res, authOptions),
session: await unstable_getServerSession(
context.req,
context.res,
authOptions
),
},
}
}

View File

@@ -12,7 +12,5 @@ declare namespace NodeJS {
GOOGLE_SECRET: string
AUTH0_ID: string
AUTH0_SECRET: string
DESCOPE_ID: string
DESCOPE_SECRET: string
}
}

View File

@@ -3,7 +3,7 @@ import { Protected } from "~/components";
export const { routeData, Page } = Protected((session) => {
return (
<main class="flex flex-col gap-2 items-center">
<h1>This is a protected route</h1>
<h1>This is a proteced route</h1>
</main>
);
});

View File

@@ -10,13 +10,13 @@
"clean": "gatsby clean"
},
"dependencies": {
"dotenv": "16.0.0",
"gatsby": "5.8.0-next.3",
"dotenv": "^16.0.0",
"gatsby": "next",
"next-auth": "workspace:*",
"react": "18.2.0",
"react-dom": "18.2.0"
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"vercel": "23.1.2"
"vercel": "^23.1.2"
}
}

View File

@@ -1,4 +1,4 @@
import { Session } from "@auth/core/types"
import { Session } from "@auth/core"
export default function useSession() {
return useState<Session | null>("session", () => null)

View File

@@ -43,7 +43,7 @@ export async function signIn<
// TODO: Handle custom base path
// TODO: Remove this since Sveltekit offers the CSRF protection via origin check
const { csrfToken } = await $fetch<{ csrfToken: string }>("/api/auth/csrf")
const { csrfToken } = await $fetch("/api/auth/csrf")
console.log(_signInUrl)

View File

@@ -1,14 +1,13 @@
import { AuthConfig, Session } from "@auth/core/types"
import { Auth } from "@auth/core"
import { AuthHandler, AuthOptions, Session } from "@auth/core"
import { fromNodeMiddleware, H3Event } from "h3"
import getURL from "requrl"
import { createMiddleware } from "@hattip/adapter-node"
export function NuxtAuthHandler(options: AuthConfig) {
export function NuxtAuthHandler(options: AuthOptions) {
async function handler(ctx: { request: Request }) {
options.trustHost ??= true
return Auth(ctx.request, options)
return AuthHandler(ctx.request, options)
}
const middleware = createMiddleware(handler)
@@ -18,7 +17,7 @@ export function NuxtAuthHandler(options: AuthConfig) {
export async function getSession(
event: H3Event,
options: AuthConfig
options: AuthOptions
): Promise<Session | null> {
options.trustHost ??= true
@@ -31,7 +30,7 @@ export async function getSession(
nodeHeaders.append(key, headers[key] as any)
})
const response = await Auth(
const response = await AuthHandler(
new Request(url, { headers: nodeHeaders }),
options
)

View File

@@ -1,21 +1,22 @@
{
"name": "next-auth-nuxt",
"name": "playground-nuxt",
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt prepare && nuxt dev",
"dev": "export NODE_OPTIONS='--no-experimental-fetch' && nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview"
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"devDependencies": {
"@nuxt/eslint-config": "^0.1.1",
"eslint": "^8.29.0",
"h3": "1.6.6",
"nuxt": "3.5.1"
"h3": "1.0.2",
"nuxt": "3.0.0"
},
"dependencies": {
"@auth/core": "workspace:*",
"@hattip/adapter-node": "^0.0.34",
"@hattip/adapter-node": "^0.0.22",
"requrl": "^3.0.2"
}
}

View File

@@ -1,4 +1,4 @@
import { Session } from "@auth/core/types"
import { Session } from "@auth/core"
export default defineNuxtPlugin(async () => {
const session = useSession()

View File

@@ -1,10 +1,10 @@
import { NuxtAuthHandler } from "@/lib/auth/server"
import GithubProvider from "@auth/core/providers/github"
import type { AuthConfig } from "@auth/core"
import type { AuthOptions } from "@auth/core"
const runtimeConfig = useRuntimeConfig()
export const authOptions = {
export const authOptions: AuthOptions = {
secret: runtimeConfig.secret,
providers: [
GithubProvider({
@@ -12,6 +12,6 @@ export const authOptions = {
clientSecret: runtimeConfig.github.clientSecret,
}),
],
} as AuthConfig
}
export default NuxtAuthHandler(authOptions)

View File

@@ -0,0 +1,19 @@
<!-- <svg width="1077" height="990" viewBox="0 0 1077 990" fill="none" xmlns="http://www.w3.org/2000/svg"> -->
<!-- <path d="M909.079 176.687C486.468 1429.32 94.8519 797.145 -245.666 989.191L-273.518 122.311C-98.0847 109.104 255.784 81.3405 267.79 75.9479C282.798 69.2072 1153.31 -485.661 1071.51 -140.889C1022.28 -26.4819 1001.45 -97.0987 909.079 176.687Z" fill="url(#paint0_linear_228_76)" fill-opacity="1" /> -->
<!-- <defs> -->
<!-- <linearGradient id="paint0_linear_228_76" x1="588.47" y1="791.265" x2="238.137" y2="497.91" gradientUnits="userSpaceOnUse"> -->
<!-- <stop stop-color="#FF4400"/> -->
<!-- <stop offset="1" stop-color="#111" /> -->
<!-- </linearGradient> -->
<!-- </defs> -->
<!-- </svg> -->
<svg width="1056" height="855" viewBox="0 0 1056 855" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M888.078 42.3027C465.467 1294.94 73.851 662.761 -266.667 854.807L-294.519 -12.0732C-119.086 -25.2807 234.783 -53.0438 246.789 -58.4364C261.797 -65.1771 1132.31 -620.045 1050.51 -275.273C1001.28 -160.866 980.447 -231.483 888.078 42.3027Z" fill="url(#paint0_radial_228_76)"/>
<defs>
<radialGradient id="paint0_radial_228_76" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(666.999 571.116) rotate(-129.513) scale(513.947 862.777)">
<stop stop-color="#FF4400"/>
<stop offset="1" stop-color="#111111"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,20 @@
<!-- <svg width="1405" height="1025" viewBox="0 0 1405 1025" fill="none" xmlns="http://www.w3.org/2000/svg"> -->
<!-- <path d="M1180.05 977.4C483.802 708.04 103.247 143.506 0 -105.091L1102.61 -196C1418.53 307.367 1876.29 1246.76 1180.05 977.4Z" fill="url(#paint0_linear_228_83)" fill-opacity="0.44"/> -->
<!-- <defs> -->
<!-- <linearGradient id="paint0_linear_228_83" x1="185.171" y1="460.565" x2="641.405" y2="-62.9701" gradientUnits="userSpaceOnUse"> -->
<!-- <stop stop-color="#BB44CC"/> -->
<!-- <stop offset="1" stop-color="#BB44CC" stop-opacity="0"/> -->
<!-- </linearGradient> -->
<!-- </defs> -->
<!-- </svg> -->
<svg width="1405" height="1025" viewBox="0 0 1405 1025" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1180.05 977.4C483.802 708.04 103.247 143.506 0 -105.091L1102.61 -196C1418.53 307.367 1876.29 1246.76 1180.05 977.4Z" fill="url(#paint0_radial_228_83)"/>
<defs>
<radialGradient id="paint0_radial_228_83" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(498 619) rotate(-46.5881) scale(548.572 1194.34)">
<stop stop-color="#BB44CC"/>
<stop offset="1" stop-color="#111111"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,20 @@
<!-- <svg width="2402" height="275" viewBox="0 0 2402 275" fill="none" xmlns="http://www.w3.org/2000/svg"> -->
<!-- <path d="M305.668 155.376C1150.52 -387.169 3270.85 708.01 2011.14 262.274L1847.48 804.627C1613.84 721.267 1142.31 553.804 1125.29 550.832C1104 547.117 -168.361 431.431 18.8067 264.119C109.949 220.108 121.011 273.958 305.668 155.376Z" fill="url(#paint0_linear_228_78)" fill-opacity="0.66"/> -->
<!-- <defs> -->
<!-- <linearGradient id="paint0_linear_228_78" x1="869.047" y1="-51.7999" x2="921.583" y2="353.448" gradientUnits="userSpaceOnUse"> -->
<!-- <stop stop-color="#44BBCC"/> -->
<!-- <stop offset="1" stop-color="#44BBCC" stop-opacity="0.12"/> -->
<!-- </linearGradient> -->
<!-- </defs> -->
<!-- </svg> -->
<svg width="2402" height="275" viewBox="0 0 2402 275" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M305.668 155.376C1150.52 -387.169 3270.85 708.01 2011.14 262.274L1847.48 804.627C1613.84 721.267 1142.31 553.804 1125.29 550.832C1104 547.117 -168.361 431.431 18.8067 264.119C109.949 220.108 121.011 273.958 305.668 155.376Z" fill="url(#paint0_radial_228_78)"/>
<defs>
<radialGradient id="paint0_radial_228_78" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(883.5 -66) rotate(93.0267) scale(757.557 879.417)">
<stop stop-color="#44BBCC"/>
<stop offset="1" stop-color="#111111"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,66 @@
import { useState, useEffect } from "react"
import { motion } from "framer-motion"
const clamp = (num, min, max) => Math.min(Math.max(num, min), max)
export default function Marquee({ files }) {
const [{ width, height }, setSize] = useState({ height: 0, width: 0 })
useEffect(() => {
setSize({
width: window.innerWidth,
height: window.innerHeight,
})
}, [])
if (width === 0) return
return (
<div className="absolute w-full h-full top-0 left-0 dark:opacity-5 opacity-20 saturate-0 brightness-75 dark:brightness-[1000] -z-20 overflow-hidden flex flex-col marquee-wrapper">
{files.map((name) => (
<MarqueeItem key={name} name={name} width={width} height={height} />
))}
</div>
)
}
function MarqueeItem({ name, width, height }) {
const [y] = useState(height * Math.random())
const [reset, setReset] = useState(false)
const [duration] = useState(clamp(Math.random() * 25, 20, 25))
useEffect(() => {
const timeout =
setTimeout(() => {
setReset(true)
}, (1000 * (width - offset)) / duration) - 2000
return () => clearTimeout(timeout)
}, [duration, width])
const offset = Math.random() * width
return (
<motion.span
key={name + reset}
className="absolute flex items-center justify-center"
initial={{ x: reset ? 0 : offset, y }}
animate={{ x: width }}
transition={{
repeat: Infinity,
duration: reset ? duration : (width - offset) / duration,
ease: "linear",
}}
>
<motion.img
src={`/img/providers/${name}`}
alt={name}
className="relative w-12 drop-shadow-xl"
initial={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
transition={{
delay: reset ? 0 : Math.random() * 5,
duration: 1,
type: "spring",
stiffness: 150,
}}
/>
</motion.span>
)
}

View File

@@ -0,0 +1,61 @@
import { Sandpack } from "@codesandbox/sandpack-react"
import { freeCodeCampDark } from "@codesandbox/sandpack-themes"
export default function AuthSandbox() {
return (
<div style={{ width: "100%" }}>
<Sandpack
template="nextjs"
files={{
"/pages/api/auth/[...nextauth].js": `import NextAuth from "next-auth"
import GithubProvider from "next-auth/providers/github"
export const authOptions = {
// Configure one or more authentication providers
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
],
}
export default NextAuth(authOptions)
`,
"pages/_app.js": `import { SessionProvider } from "next-auth/react"
export default function App({
Component,
pageProps: { session, ...pageProps },
}) {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
)
}`,
".env": `GITHUB_ID=123abc
GITHUB_SECRET=abc123
NEXTAUTH_SECRET=abcbaerjwiofjlakshfu9fnajs
`,
"/pages/index.js": `export default function App() {
return <div><h1>Hello NextAuth User</h1><a href="/api/auth/signin">Signin</a></div>
}`,
}}
theme={freeCodeCampDark}
customSetup={{
dependencies: {
"next-auth": "latest",
},
}}
options={{
showInlineErrors: true,
showLineNumbers: true,
showTabs: true,
closableTabs: true,
showNavigator: true,
showConsole: true,
showConsoleButton: true,
editorHeight: "70vh",
}}
/>
</div>
)
}

View File

@@ -0,0 +1,85 @@
export default function Triangle({ className }) {
return (
<svg
id="triangle"
viewBox="0 0 445 379"
className={className}
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<style jsx>
{`
@media (prefers-color-scheme: light) {
#triangle-0 {
fill: #000;
}
#triangle-1 {
fill: #ff4400;
}
#triangle-2 {
fill: #bb44cc;
}
#triangle-3 {
fill: #44bbcc;
}
}
`}
</style>
<path
id="triangle-0"
d="M334.033 238.33L222.692 46.7201L111.352 238.33H334.033Z"
fill="#fff"
/>
<path
id="triangle-1"
d="M198.174 1.12812L4.62255 51.2473C1.37638 52.0878 -0.550786 55.4258 0.344333 58.6574L53.7157 251.338C55.1362 256.466 62.0336 257.344 64.6942 252.736L204.874 9.93656C207.535 5.32834 203.325 -0.205751 198.174 1.12812Z"
fill="url(#triangle-1-gradient)"
/>
<path
id="triangle-2"
d="M246.294 1.12825L439.846 51.2473C443.092 52.0879 445.019 55.4259 444.124 58.6574L390.753 251.338C389.332 256.466 382.435 257.344 379.774 252.736L239.594 9.93665C236.933 5.32844 241.143 -0.205634 246.294 1.12825Z"
fill="url(#triangle-2-gradient)"
/>
<path
id="triangle-3"
d="M361.388 286.577L225.585 377.959C223.559 379.322 220.91 379.322 218.885 377.959L83.0814 286.577C78.1672 283.27 80.5079 275.599 86.4311 275.599H358.039C363.962 275.599 366.303 283.27 361.388 286.577Z"
fill="url(#triangle-3-gradient)"
/>
<defs>
<linearGradient
id="triangle-1-gradient"
x1="-1.28386"
y1="52.7769"
x2="134.785"
y2="131.336"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#992900" />
<stop offset="1" stopColor="#FF4400" />
</linearGradient>
<linearGradient
id="triangle-2-gradient"
x1="445.753"
y1="52.7769"
x2="309.684"
y2="131.336"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#6D2178" />
<stop offset="1" stopColor="#BB44CC" />
</linearGradient>
<linearGradient
id="triangle-3-gradient"
x1="222.236"
y1="380.213"
x2="222.236"
y2="275.599"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#1B5B64" />
<stop offset="1" stopColor="#44BBCC" />
</linearGradient>
</defs>
</svg>
)
}

View File

@@ -0,0 +1,26 @@
import withNextra from "nextra"
import RemarkLinkRewrite from "remark-link-rewrite"
export default withNextra({
theme: "nextra-theme-docs",
themeConfig: "./theme.config.jsx",
mdxOptions: {
format: "detect",
remarkPlugins: [
[
RemarkLinkRewrite,
{
replacer(url) {
if (url.includes(".md")) {
url = url.replace(/\.md$/, "").replace(".md#", "#")
}
if (url.startsWith("https://authjs.dev")) {
url = url.replace("https://authjs.dev", "")
}
return url
},
},
],
],
},
})()

31
docs-nextra/package.json Normal file
View File

@@ -0,0 +1,31 @@
{
"name": "docs-nextra",
"version": "0.0.1",
"description": "",
"main": "index.js",
"scripts": {
"typedoc": "typedoc",
"dev": "TYPEDOC_WATCH=1 pnpm typedoc & next dev",
"start": "next start",
"build": "next build"
},
"keywords": [],
"author": "ndom91 <yo@ndo.dev> (https://ndo.dev/)",
"license": "MIT",
"dependencies": {
"@codesandbox/sandpack-react": "^2.0.6",
"@codesandbox/sandpack-themes": "^2.0.1",
"framer-motion": "^8.4.3",
"next": "13.1.1",
"nextra": "^2.2.5",
"nextra-theme-docs": "^2.2.5",
"remark-link-rewrite": "^1.0.6",
"typedoc": "^0.23.24",
"typedoc-plugin-markdown": "next"
},
"devDependencies": {
"autoprefixer": "^10.4.13",
"postcss": "^8.4.21",
"tailwindcss": "^3.2.4"
}
}

View File

@@ -0,0 +1,5 @@
import "../styles/index.css"
export default function NextraApp({ Component, pageProps }) {
return ( <Component {...pageProps} />)
}

View File

@@ -0,0 +1,32 @@
{
"docs": {
"title": "Docs",
"type": "page",
"href": "/getting-started/introduction"
},
"frameworks": {
"title": "Frameworks",
"type": "page",
"href": "/reference"
},
"index": {
"title": "Home",
"theme": {
"sidebar": false,
"layout": "raw"
}
},
"getting-started": "Getting Started",
"guides": "Guides",
"reference": "Reference",
"concepts": "Concepts",
"--": {
"type": "separator"
},
"contributors": "Contributors",
"github-link": {
"title": "Auth.js Repo",
"href": "https://github.com/nextauthjs/next-auth",
"newWindow": true
}
}

View File

@@ -0,0 +1,4 @@
{
"faq": "FAQ",
"oauth": "OAuth"
}

View File

@@ -0,0 +1,359 @@
---
id: faq
title: Frequently Asked Questions
---
## About Auth.js
### Is Auth.js commercial software?
Auth.js is an open source project built by individual contributors.
It is not commercial software and is not associated with a commercial organization.
---
## Compatibility
<details>
<summary>
<h3 style={{display:"inline-block"}}>What databases does Auth.js support?</h3>
</summary>
<p>
You can use Auth.js with MySQL, MariaDB, Postgres, MongoDB and SQLite or without a database. (See our [using a database adapter guide](/guides/adapters/using-a-database-adapter)).
You can use also Auth.js with any database using a custom database adapter, or by using a custom credentials authentication provider - e.g. to support signing in with a username and password stored in an existing database.
</p>
</details>
<details>
<summary>
<h3 style={{display:"inline-block"}}>What authentication services does Auth.js support?</h3>
</summary>
<p>
<p>Auth.js includes built-in support for signing in with&nbsp;
(See also: <a href="/reference/providers/oauth-builtin">Providers</a>)
</p>
Auth.js also supports email for passwordless sign in, which is useful for account recovery or for people who are not able to use an account with the configured OAuth services (e.g. due to service outage, account suspension or otherwise becoming locked out of an account).
You can also use a custom based provider to support signing in with a username and password stored in an external database and/or using two factor authentication.
</p>
</details>
<details>
<summary>
<h3 style={{display:"inline-block"}}>Does Auth.js support signing in with a username and password?</h3>
</summary>
<p>
Auth.js is designed to avoid the need to store passwords for user accounts.
If you have an existing database of usernames and passwords, you can use a custom credentials provider to allow signing in with a username and password stored in an existing database.
_If you use a custom credentials provider user accounts will not be persisted in a database by Auth.js (even if one is configured). The option to use JSON Web Tokens for session tokens (which allow sign in without using a session database) must be enabled to use a custom credentials provider._
</p>
</details>
<details>
<summary>
<h3 style={{display:"inline-block"}}>Can I use Auth.js with a website that does not use Next.js?</h3>
</summary>
<p>
Auth.js is designed for use with Next.js and Serverless.
If you are using a different framework for your website, you can create a website that handles sign in with Next.js and then access those sessions on a website that does not use Next.js as long as the websites are on the same domain.
If you use Auth.js on a website with a different subdomain then the rest of your website (e.g. `auth.example.com` vs `www.example.com`) you will need to set a custom cookie domain policy for the Session Token cookie. (See also: [Cookies](/reference/configuration/auth-config#cookies))
Auth.js does not currently support automatically signing into sites on different top level domains (e.g. `www.example.com` vs `www.example.org`) using a single session.
</p>
</details>
<details>
<summary>
<h3 style={{display:"inline-block"}}>Can I use Auth.js with React Native?</h3>
</summary>
<p>
Auth.js is designed as a secure, confidential client and implements a server side authentication flow.
It is not intended to be used in native applications on desktop or mobile applications, which typically implement public clients (e.g. with client / secrets embedded in the application).
</p>
</details>
<details>
<summary>
<h3 style={{display:"inline-block"}}>Is Auth.js supporting TypeScript?</h3>
</summary>
<p>
Yes! Check out the [TypeScript docs](/getting-started/typescript)
</p>
</details>
<details>
<summary>
<h3 style={{display:"inline-block"}}>Is Auth.js compatible with Next.js 12 Middleware?</h3>
</summary>
<p>
[Next.js Middleware](https://nextjs.org/docs/middleware) is supported. Head over to the [this page](/reference/nextjs/#middleware)
</p>
</details>
---
## Databases
<details>
<summary>
<h3 style={{display:"inline-block"}}>What databases are supported by Auth.js?</h3>
</summary>
<p>
Auth.js can be used with MySQL, Postgres, MongoDB, SQLite and compatible databases (e.g. MariaDB, Amazon Aurora, Amazon DocumentDB…) or with no database.
It also provides an Adapter API which allows you to connect it to any database.
</p>
</details>
<details>
<summary>
<h3 style={{display:"inline-block"}}>What does Auth.js use databases for?</h3>
</summary>
<p>
Databases in Auth.js are used for persisting users, OAuth accounts, email sign in tokens and sessions.
Specifying a database is optional if you don't need to persist user data or support email sign in. If you don't specify a database then JSON Web Tokens will be enabled for session storage and used to store session data.
If you are using a database with Auth.js, you can still explicitly enable JSON Web Tokens for sessions (instead of using database sessions).
</p>
</details>
<details>
<summary>
<h3 style={{display:"inline-block"}}>Should I use a database?</h3>
</summary>
<p>
- Using Auth.js without a database works well for internal tools - where you need to control who is able to sign in, but when you do not need to create user accounts for them in your application.
- Using Auth.js with a database is usually a better approach for a consumer facing application where you need to persist accounts (e.g. for billing, to contact customers, etc).
</p>
</details>
<details>
<summary>
<h3 style={{display:"inline-block"}}>What database should I use?</h3>
</summary>
<p>
Managed database solutions for MySQL, Postgres and MongoDB (and compatible databases) are well supported from cloud providers such as Amazon, Google, Microsoft and Atlas.
If you are deploying directly to a particular cloud platform you may also want to consider serverless database offerings they have (e.g. [Amazon Aurora Serverless on AWS](https://aws.amazon.com/rds/aurora/serverless/)).
</p>
</details>
---
## Security
Parts of this section has been moved to its [own page](/getting-started/security).
<details>
<summary>
<h3 style={{display:"inline-block"}}>How do I get Refresh Tokens and Access Tokens for an OAuth account?</h3>
</summary>
<p>
Auth.js provides a solution for authentication, session management and user account creation.
Auth.js records Refresh Tokens and Access Tokens on sign in (if supplied by the provider) and it will pass them, along with the User ID, Provider and Provider Account ID, to either:
1. A database - if a database connection string is provided
2. The JSON Web Token callback - if JWT sessions are enabled (e.g. if no database specified)
You can then look them up from the database or persist them to the JSON Web Token.
Note: Auth.js does not currently handle Access Token rotation for OAuth providers for you, however you can check out [this tutorial](/guides/basics/refresh-token-rotation) if you want to implement it.
We also have an [example repository](https://github.com/nextauthjs/next-auth-refresh-token-example) / project based upon Auth.js v4 where we demonstrate how to use a refresh token to refresh the provided access token.
</p>
</details>
<details>
<summary>
<h3 style={{display:"inline-block"}}>When I sign in with another account with the same email address, why are accounts not linked automatically?</h3>
</summary>
<p>
Automatic account linking on sign in is not secure between arbitrary providers - with the exception of allowing users to sign in via an email addresses as a fallback (as they must verify their email address as part of the flow).
When an email address is associated with an OAuth account it does not necessarily mean that it has been verified as belonging to account holder — how email address verification is handled is not part of the OAuth specification and varies between providers (e.g. some do not verify first, some do verify first, others return metadata indicating the verification status).
With automatic account linking on sign in, this can be exploited by bad actors to hijack accounts by creating an OAuth account associated with the email address of another user.
For this reason it is not secure to automatically link accounts between arbitrary providers on sign in, which is why this feature is generally not provided by authentication service and is not provided by Auth.js.
Automatic account linking is seen on some sites, sometimes insecurely. It can be technically possible to do automatic account linking securely if you trust all the providers involved to ensure they have securely verified the email address associated with the account, but requires placing trust (and transferring the risk) to those providers to handle the process securely.
Examples of scenarios where this is secure include with an OAuth provider you control (e.g. that only authorizes users internal to your organization) or with a provider you explicitly trust to have verified the users email address.
Automatic account linking is not a planned feature of Auth.js, however there is scope to improve the user experience of account linking and of handling this flow, in a secure way. Typically this involves providing a fallback option to sign in via email, which is already possible (and recommended), but the current implementation of this flow could be improved on.
Providing support for secure account linking and unlinking of additional providers - which can only be done if a user is already signed in already - was originally a feature in v1.x but has not been present since v2.0, is planned to return in a future release.
</p>
</details>
---
## Feature Requests
<details>
<summary>
<h3 style={{display:"inline-block"}}>Why doesn't Auth.js support [a particular feature]?</h3>
</summary>
<p>
Auth.js is an open source project built by individual contributors who are volunteers writing code and providing support in their spare time.
If you would like Auth.js to support a particular feature, the best way to help make it happen is to raise a feature request describing the feature and offer to work with other contributors to develop and test it.
If you are not able to develop a feature yourself, you can offer to sponsor someone to work on it.
</p>
</details>
<details>
<summary>
<h3 style={{display:"inline-block"}}>I disagree with a design decision, how can I change your mind?</h3>
</summary>
<p>
Product design decisions on Auth.js are made by core team members.
You can raise suggestions as feature requests / requests for enhancement.
Requests that provide the detail requested in the template and follow the format requested may be more likely to be supported, as additional detail prompted in the templates often provides important context.
Ultimately if your request is not accepted or is not actively in development, you are always free to fork the project under the terms of the ISC License.
</p>
</details>
---
## JSON Web Tokens
<details>
<summary>
<h3>Does Auth.js use JSON Web Tokens?</h3>
</summary>
<p>
Auth.js by default uses JSON Web Tokens for saving the user's session. However, if you use a [database adapter](/guides/adapters/using-a-database-adapter), the database will be used to persist the user's session. You can force the usage of JWT when using a database [through the configuration options](/reference/configuration/auth-config#session). Since v4 all our JWT tokens are now encrypted by default with A256GCM.
</p>
</details>
<details>
<summary>
<h3>What are the advantages of JSON Web Tokens?</h3>
</summary>
<p>
JSON Web Tokens can be used for session tokens, but are also used for lots of other things, such as sending signed objects between services in authentication flows.
- Advantages of using a JWT as a session token include that they do not require a database to store sessions, this can be faster and cheaper to run and easier to scale.
- JSON Web Tokens in Auth.js are secured using cryptographic encryption (JWE) to store the included information directly in a JWT session token. You may then use the token to pass information between services and APIs on the same domain without having to contact a database to verify the included information.
- You can use JWT to securely store information you do not mind the client knowing even without encryption, as the JWT is stored in a server-readable-only cookie so data in the JWT is not accessible to third party JavaScript running on your site.
</p>
</details>
<details>
<summary>
<h3>What are the disadvantages of JSON Web Tokens?</h3>
</summary>
<p>
- You cannot as easily expire a JSON Web Token - doing so requires maintaining a server side blocklist of invalid tokens (at least until they expire) and checking every token against the list every time a token is presented.
Shorter session expiry times are used when using JSON Web Tokens as session tokens to allow sessions to be invalidated sooner and simplify this problem.
Auth.js client includes advanced features to mitigate the downsides of using shorter session expiry times on the user experience, including automatic session token rotation, optionally sending keep alive messages to prevent short lived sessions from expiring if there is an window or tab open, background re-validation, and automatic tab/window syncing that keeps sessions in sync across windows any time session state changes or a window or tab gains or loses focus.
- As with database session tokens, JSON Web Tokens are limited in the amount of data you can store in them. There is typically a limit of around 4096 bytes per cookie, though the exact limit varies between browsers, proxies and hosting services. If you want to support most browsers, then do not exceed 4096 bytes per cookie. If you want to save more data, you will need to persist your sessions in a database (Source: [browsercookielimits.iain.guru](http://browsercookielimits.iain.guru/))
The more data you try to store in a token and the more other cookies you set, the closer you will come to this limit. Since v4 we have implemented cookie chunking so that cookies over the 4kb limit get split and reassembled upon parsing. However since this data needs to be transmitted on every request, if you wish to store more than ~4 KB of data you're probably at the point where you want to store a unique ID in the token and persist the data elsewhere (e.g. in a server-side key/value store).
- Data stored in an encrypted JSON Web Token (JWE) may be compromised at some point.
Even if appropriately configured, information stored in an encrypted JWT should not be assumed to be impossible to decrypt at some point - e.g. due to the discovery of a defect or advances in technology.
Avoid storing any data in a token that might be problematic if it were to be decrypted in the future.
- If you do not explicitly specify a secret for for Auth.js, existing sessions will be invalidated any time your Auth.js configuration changes, as Auth.js will default to an auto-generated secret. Since v4 this only impacts development and generating a secret is required in production.
</p>
</details>
<details>
<summary>
<h3>Are JSON Web Tokens secure?</h3>
</summary>
<p>
By default tokens are not signed (JWS) but are encrypted (JWE). Since v4 we have implemented cookie chunking so that cookies over the 4kb limit get split and reassembled upon parsing.
You can specify other valid algorithms - [as specified in RFC 7518](https://tools.ietf.org/html/rfc7517) - with either a secret (for symmetric encryption) or a public/private key pair (for asymmetric encryption).
Auth.js will generate keys for you, but this will generate a warning at start up.
Using explicit public/private keys for signing is strongly recommended.
</p>
</details>
<details>
<summary>
<h3>What signing and encryption standards does Auth.js support?</h3>
</summary>
<p>
Auth.js includes a largely complete implementation of JSON Object Signing and Encryption (JOSE):
- [RFC 7515 - JSON Web Signature (JWS)](https://tools.ietf.org/html/rfc7515)
- [RFC 7516 - JSON Web Encryption (JWE)](https://tools.ietf.org/html/rfc7516)
- [RFC 7517 - JSON Web Key (JWK)](https://tools.ietf.org/html/rfc7517)
- [RFC 7518 - JSON Web Algorithms (JWA)](https://tools.ietf.org/html/rfc7518)
- [RFC 7519 - JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519)
This incorporates support for:
- [RFC 7638 - JSON Web Key Thumbprint](https://tools.ietf.org/html/rfc7638)
- [RFC 7787 - JSON JWS Unencoded Payload Option](https://tools.ietf.org/html/rfc7797)
- [RFC 8037 - CFRG Elliptic Curve ECDH and Signatures](https://tools.ietf.org/html/rfc8037)
</p>
</details>

View File

@@ -0,0 +1,53 @@
---
title: How OAuth works
---
import { Callout } from 'nextra-theme-docs'
Authentication Providers in **Auth.js** are OAuth definitions that allow your users to sign in with their favorite preexisting logins. You can use any of our many predefined providers, or write your own custom OAuth configuration.
- [Using a built-in OAuth Provider](#built-in-providers) (e.g Github, Twitter, Google, etc...)
- [Using a custom OAuth Provider](#using-a-custom-provider)
<Callout>
Auth.js is designed to work with any OAuth service, it supports **OAuth 1.0**, **1.0A**, **2.0** and **OpenID Connect** and has built-in support for most popular sign-in services.
</Callout>
Without going into too much detail, the OAuth flow generally has 6 parts:
1. The application requests authorization to access service resources from the user
2. If the user authorized the request, the application receives an authorization grant
3. The application requests an access token from the authorization server (API) by presenting authentication of its own identity, and the authorization grant
4. If the application identity is authenticated and the authorization grant is valid, the authorization server (API) issues an access token to the application. Authorization is complete.
5. The application requests the resource from the resource server (API) and presents the access token for authentication
6. If the access token is valid, the resource server (API) serves the resource to the application
```mermaid
sequenceDiagram
participant Browser
participant App Server
participant Auth Server (Github)
Note left of Browser: User clicks on "Sign in"
Browser->>App Server: GET<br/>"api/auth/signin"
App Server->>App Server: Computes the available<br/>sign in providers<br/>from the "providers" option
App Server->>Browser: Redirects to Sign in page
Note left of Browser: Sign in options<br/>are shown the user<br/>(Github, Twitter, etc...)
Note left of Browser: User clicks on<br/>"Sign in with Github"
Browser->>App Server: POST<br/>"api/auth/signin/github"
App Server->>App Server: Computes sign in<br/>options for Github<br/>(scopes, callback URL, etc...)
App Server->>Auth Server (Github): GET<br/>"github.com/login/oauth/authorize"
Note left of Auth Server (Github): Sign in options<br> are supplied as<br/>query params<br/>(clientId, <br/>scope, etc...)
Auth Server (Github)->>Browser: Shows sign in page<br/>in Github.com<br/>to the user
Note left of Browser: User inserts their<br/>credentials in Github
Browser->>Auth Server (Github): Github validates the inserted credentials
Auth Server (Github)->>Auth Server (Github): Generates one time access code<br/>and calls callback<br>URL defined in<br/>App settings
Auth Server (Github)->>App Server: GET<br/>"api/auth/github/callback?code=123"
App Server->>App Server: Grabs code<br/>to exchange it for<br/>access token
App Server->>Auth Server (Github): POST<br/>"github.com/login/oauth/access_token"<br/>{code: 123}
Auth Server (Github)->>Auth Server (Github): Verifies code is<br/>valid and generates<br/>access token
Auth Server (Github)->>App Server: { access_token: 16C7x... }
App Server->>App Server: Generates session token<br/>and stores session
App Server->>Browser: You're now logged in!
```
For more details, check out Aaron Parecki's blog post [OAuth2 Simplified](https://aaronparecki.com/oauth-2-simplified/) or Postman's blog post [OAuth 2.0: Implicit Flow is Dead, Try PKCE Instead](https://blog.postman.com/pkce-oauth-how-to/).

View File

@@ -0,0 +1,41 @@
## Core team
Without these people, the project could not have become one of the most used authentication libraries in its category.
- [Balázs Orbán](https://github.com/balazsorban44) - **Lead Maintainer**
- [Thang Vu](https://github.com/ThangHuuVu) - Maintainer (Core)
- [Nico Domino](https://github.com/ndom91) - Maintainer (Core, Documentation)
- [Lluis Agusti](https://github.com/lluia) - Maintainer (Documentation, Testing, TypeScript)
## Special thanks
Special thanks to Lori Karikari for creating most of the original provider configurations, to Fredrik Pettersen for creating the original Prisma Adapter, to Gerald Nolan for adding support for Sign in with Apple, and to Jefferson Bledsoe for working on the original testing automation.
- [Lori Karikari](https://github.com/LoriKarikari)
- [Fredrik Pettersen](https://github.com/Fumler)
- [Gerald Nolan](https://github.com/geraldnolan)
- [Jefferson Bledsoe](https://github.com/JeffersonBledsoe)
## Other contributors
Auth.js as it exists today has been possible thanks to the work of many individual contributors.
Thank you to the [dozens of individual contributors](https://github.com/nextauthjs/next-auth/graphs/contributors) who have helped shape Auth.js.
## Open Collective
You can find Auth.js on Open Collective. We are very thankful for all of our existing contributors and would be delighted if you or your company would decide to join them.
More information can be found at: https://opencollective.com/nextauth
## History
- NextAuth.js was originally developed by <a href="https://github.com/iaincollins">Iain Collins</a> in 2016 for Next.js.
- In 2020, NextAuth.js was rebuilt from the ground up to support Serverless, with support for MySQL, Postgres and MongoDB, JSON Web Tokens and built-in support for over a dozen authentication providers.
- In 2021, efforts have started to move NextAuth.js to other frameworks and to support as many databases and providers as possible.
- In 2022, NextAuth.js was rebranded to Auth.js and became framework/runtime agnostic. The core library `@auth/core` was written by <a href="https://twitter.com/balazsorban44">Balázs Orbán</a>
- In 2023 // TODO: 👀🤫

View File

@@ -0,0 +1,12 @@
{
"credentials-tutorial": "Credentials Authentication",
"databases": "Databases",
"email-tutorial": "Email Authentication",
"introduction": "Introduction",
"oauth-tutorial": "OAuth Authentication",
"typescript": "Typescript",
"security": "Security",
"img": {
"display": "hidden"
}
}

View File

@@ -1,7 +1,9 @@
---
title: Credentials authentication
title: Credentials Authentication
---
import { Callout } from 'nextra-theme-docs'
Auth.js is built in a way that is flexible to integrate it with any authentication back-end you or your company may already have.
This library has been designed to handle the user session client-wise, to support multiple authentication methods (OAuth, Email, etc...) so that you're not forced to run your own authentication service.
@@ -10,9 +12,9 @@ In case you already have an authentication service, you can use the Credentials
For this tutorial, we're going to use [Auth.js example app](https://github.com/nextauthjs/next-auth-example) as a base.
:::warning
<Callout type="warning">
The functionality provided for credentials based authentication is intentionally limited to discourage use of passwords due to the inherent security risks associated with them and the additional complexity associated with supporting usernames and passwords.
:::
</Callout>
Integrating the Credentials Provider is as simple as initializing it in the Auth.js configuration file:
@@ -45,12 +47,12 @@ export default NextAuth({
})
```
:::note
Check the [Credentials Provider options](/reference/core/providers_credentials) for further customization
:::
<Callout type="info">
Check the [Credentials Provider options](/reference/providers/credentials) for further customization
</Callout>
Note that we only need to define an `authorize` method that is in charge of receiving the credentials inserted by the user and call the authorization service.
:::info
<Callout type="info">
If you're using TypeScript, you can [augment the `User` interface](/getting-started/typescript#module-augmentation) to match the response of your `authorize` callback, so whenever you read the user in other callbacks (like the `jwt`) the type will match correctly.
:::
</Callout>

View File

@@ -0,0 +1,22 @@
---
title: Databasess
---
import { Callout } from 'nextra-theme-docs'
Auth.js offers multiple database adapters. Check our guides on:
- [Using an existing database adapter](/guides/adapters/using-a-database-adapter)
- [Creating your own](/guides/adapters/creating-a-database-adapter)
<Callout type="info">
As of **v4** Auth.js no longer ships with an adapter included by default. If you would like to persist any information, you need to install one of the many available adapters yourself. See the individual adapter documentation pages for more details.
</Callout>
To learn more about databases in Auth.js and how they are used, check out [databases in the FAQ](/concepts/faq#databases).
---
## How to use a database
See the [documentation for adapters](/reference/adapters/overview) for more information on advanced configuration, including how to use Auth.js with other databases using a [custom adapter](/guides/adapters/creating-a-database-adapter).

View File

@@ -2,6 +2,9 @@
title: Email authentication
---
import { Callout } from "nextra-theme-docs"
import Image from "next/image"
import smtpConfig from "./img/dashboard-smtp.png"
import startPageImg from "./img/email-tutorial-start.png"
import checkPageImg from "./img/email-tutorial-check.png"
@@ -18,9 +21,12 @@ The Email provider can be used in conjunction with (or instead of) one or more O
On initial sign in, a **Verification Token** is sent to the email address provided. By default this token **is valid for 24 hours**. If the Verification Token is used within that time (i.e. by clicking on the link in the email) an account is created for the user and they are signed in.
:::tip
The Email Provider can be used with both JSON Web Tokens and database sessions, but you [must configure a database adapter](/guides/adapters/using-a-database-adapter) to use it. It is not possible to enable email sign in without using a database.
:::
<Callout>
The Email Provider can be used with both JSON Web Tokens and database
sessions, but you [must configure a database
adapter](/guides/adapters/using-a-database-adapter) to use it. It is not
possible to enable email sign in without using a database.
</Callout>
## 1. Installing `nodemailer`
@@ -34,15 +40,16 @@ npm install -D nodemailer
## 2. Setting up a SMTP service
Next we need a [SMTP service](https://sendgrid.com/blog/what-is-an-smtp-server/) which will be in charge of sending emails from our application. There's a number of services available for this, however [here are the ones](https://community.nodemailer.com/2-0-0-beta/setup-smtp/well-known-services) known to work with `nodemailer`.
Next we need a [SMTP service](https://sendgrid.com/blog/what-is-an-smtp-server/) which will be in charge of sending emails from our application. There's a number of services available for this, however [here are the ones](http://nodemailer.com/smtp/well-known/) known to work with `nodemailer`.
:::info
For this tutorial, we're going to be using [Sendgrid](https://sendgrid.com/), but any of the services linked above should work the same
:::
<Callout>
For this tutorial, we're going to be using [Sendgrid](https://sendgrid.com/),
but any of the services linked above should work the same
</Callout>
First create an account in and then login to the dashboard, then navigate to "Settings → API Keys" and create an API key:
<img src={smtpConfig} />
<Image src={smtpConfig} />
Next paste the API in your terminal as so, and run the command:
@@ -66,17 +73,17 @@ Nice! We're getting there. Now we need to read supply this values as the configu
```ts title="pages/api/auth/[...nextauth].ts"
import NextAuth from "next-auth"
import Email from "next-auth/providers/email"
import EmailProvider from "next-auth/providers/email"
export default NextAuth({
providers: [
Email({
server: {
host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT),
host: process.env.EMAIL_SERVER_HOST,
port: Number(process.env.EMAIL_SERVER_PORT),
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASSWORD,
user: process.env.EMAIL_SERVER_USER,
pass: process.env.EMAIL_SERVER_PASSWORD,
},
},
from: process.env.EMAIL_FROM,
@@ -91,12 +98,12 @@ Finally, we'll need to set up a database adapter to store verification tokens th
An **Adapter** in Auth.js connects your application to whatever database or backend system you want to use to store data for users, their accounts, sessions, etc...
For this tutorial, we're going to use the **MongoDB** adapter, but any of the other adapters will work just fine.
For this tutorial, we're going to use the **MongoDB** adapter, other any of the other adapters will work just fine.
First, let's start by installing the adapter package:
```bash npm2yarn2pnpm
npm install -D @auth/mongodb-adapter mongodb
npm install -D @next-auth/mongodb-adapter mongodb
```
and create a simple MongoDB client:
@@ -142,13 +149,13 @@ And now let's reference this new adapter from our Auth.js configuration file:
```diff title="pages/api/auth/[...nextauth].ts"
import NextAuth from "next-auth"
import EmailProvider from "next-auth/providers/email"
+ import { MongoDBAdapter } from "@auth/mongodb-adapter"
+ import { MongoDBAdapter } from "@next-auth/mongodb-adapter"
+ import clientPromise from "../../../lib/mongodb/client"
export default NextAuth({
secret: process.env.NEXTAUTH_SECRET,
+ adapter: MongoDBAdapter(clientPromise),
providers: [
+ adapter: MongoDBAdapter(clientPromise),
EmailProvider({
server: {
host: process.env.EMAIL_SERVER_HOST,
@@ -172,30 +179,34 @@ Let's start by running a Next.js application with NextAuth, making sure the **Em
For this tutorial we're going to be using NextAuth example app. Launch the app and click on "Sign in", we're redirected to the Sign In page:
<img src={startPageImg} alt="Screenshot of sign in page" />
<Image src={startPageImg} alt="Screenshot of sign in page" />
:::info
You can customize the look and feel of your Sign in page pretty easily with NextAuth. Refer to our [pages guide](/guides/basics/pages) for that!
:::
<Callout>
You can customize the look and feel of your Sign in page pretty easily with
NextAuth. Refer to our [pages guide](/guides/basics/pages) for that!
</Callout>
Then we insert the email address we want to log-in with in the Email credentials section and click on "Sign in with Email".
NextAuth will then display another page hinting the user to check their email:
<img src={checkPageImg} alt="Screenshot of check email page" />
<Image src={checkPageImg} alt="Screenshot of check email page" />
Let's now check our email, and look for one sent from NextAuth (check your spam folder just in case):
<img src={mailboxImg} alt="Screenshot of mailbox" />
<Image src={mailboxImg} alt="Screenshot of mailbox" />
Nice! We got one, coming from the sender specified in the `EMAIL_FROM` environment variable from our configuration above and that's is the sender we verified in Sendgrid.
Nice! We got one, coming from the sender specified in the `EMAIL_FROM` environment variable from our configuration above and that's is the sender we verified in Sengrid.
Click on "Sign in" and a new browser tab will open, you should then land on your application as authenticated!
<img src={loggedInImg} alt="Screenshot of logged in" />
<Image src={loggedInImg} alt="Screenshot of logged in" />
Easy right? We had to configure Sendgrid and install a database adapter so the user sessions can be saved somewhere, but once done NextAuth will deal with all the user session management for us in a secure way!
:::info
A user account (i.e. an entry in the Users table) will not be created for the user until the first time they verify their email address. If an email address is already associated with an account, the user will be signed in to that account when they use the link in the email.
:::
<Callout>
A user account (i.e. an entry in the Users table) will not be created for the
user until the first time they verify their email address. If an email address
is already associated with an account, the user will be signed in to that
account when they use the link in the email.
</Callout>

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