mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad6c13cdc9 | ||
|
|
591aa7cc7e | ||
|
|
9abb392b4e | ||
|
|
b89ae87fb1 | ||
|
|
3687d17724 | ||
|
|
b04ff82fb9 | ||
|
|
c11915ba9c | ||
|
|
24ee459f97 | ||
|
|
ac4851d238 | ||
|
|
84094b0ee7 | ||
|
|
f09ab4a04f | ||
|
|
067364381b | ||
|
|
6ee36b6842 | ||
|
|
5a89ab69d3 | ||
|
|
665445818e | ||
|
|
67cf2a11bb | ||
|
|
832d51f10e | ||
|
|
29862ac887 |
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -28,10 +28,10 @@ See [Kap](https://getkap.co/) for a good, easy-to-use, cross-platform screen rec
|
||||
|
||||
## Environment 🖥
|
||||
|
||||
Please run this command:
|
||||
Please run this command in your project's root folder:
|
||||
|
||||
```
|
||||
$ npx envinfo --system --binaries --browsers --npmPackages "{next-auth}"
|
||||
```sh
|
||||
npx envinfo --system --binaries --browsers --npmPackages "next,next-auth,react"
|
||||
```
|
||||
|
||||
and paste the output here.
|
||||
|
||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -21,11 +21,17 @@ jobs:
|
||||
- name: Dependencies
|
||||
uses: bahmutov/npm-install@v1
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
run: npm test -- --coverage --verbose && npm run test:types
|
||||
- name: Coverage
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
directory: ./coverage
|
||||
fail_ci_if_error: false
|
||||
- name: Build
|
||||
run: npm run build
|
||||
release:
|
||||
name: Release
|
||||
environment: release
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -58,4 +58,7 @@ app/yarn.lock
|
||||
/_work
|
||||
|
||||
# Prisma migrations
|
||||
/prisma/migrations
|
||||
/prisma/migrations
|
||||
|
||||
# Tests
|
||||
/coverage
|
||||
@@ -1,8 +1,11 @@
|
||||
/** @type {import('@jest/types').Config.InitialOptions} */
|
||||
module.exports = {
|
||||
transform: {
|
||||
"\\.js$": ["babel-jest", { configFile: "./config/babel.config.js" }],
|
||||
},
|
||||
roots: ["../src"],
|
||||
setupFilesAfterEnv: ["./jest-setup.js"],
|
||||
rootDir: "../src",
|
||||
setupFilesAfterEnv: ["../config/jest-setup.js"],
|
||||
collectCoverageFrom: ["!client/__tests__/**"],
|
||||
testMatch: ["**/*.test.js"],
|
||||
coverageDirectory: "../coverage",
|
||||
}
|
||||
|
||||
1755
package-lock.json
generated
1755
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -29,7 +29,6 @@
|
||||
"./errors": "./dist/lib/errors.js"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "npx husky install",
|
||||
"build": "npm run build:js && npm run build:css",
|
||||
"build:js": "node ./config/build.js && babel --config-file ./config/babel.config.js src --out-dir dist",
|
||||
"build:css": "postcss --config config/postcss.config.js src/**/*.css --base src --dir dist && node config/wrap-css.js",
|
||||
@@ -101,6 +100,7 @@
|
||||
"@testing-library/jest-dom": "^5.12.0",
|
||||
"@testing-library/react": "^11.2.6",
|
||||
"@testing-library/user-event": "^13.1.9",
|
||||
"@types/nodemailer": "^6.4.2",
|
||||
"@types/react": "^17.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
||||
"@typescript-eslint/parser": "^4.22.0",
|
||||
|
||||
64
src/client/__tests__/client-provider.test.js
Normal file
64
src/client/__tests__/client-provider.test.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import { useState } from "react"
|
||||
import { rest } from "msw"
|
||||
import { render, screen, waitFor } from "@testing-library/react"
|
||||
import { server, mockSession } from "./helpers/mocks"
|
||||
import { Provider, useSession } from ".."
|
||||
import userEvent from "@testing-library/user-event"
|
||||
|
||||
beforeAll(() => {
|
||||
server.listen()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
server.resetHandlers()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
server.close()
|
||||
})
|
||||
|
||||
test("fetches the session once and re-uses it for different consumers", async () => {
|
||||
const sessionRouteCall = jest.fn()
|
||||
|
||||
server.use(
|
||||
rest.get("/api/auth/session", (req, res, ctx) => {
|
||||
sessionRouteCall()
|
||||
res(ctx.status(200), ctx.json(mockSession))
|
||||
})
|
||||
)
|
||||
|
||||
render(<ProviderFlow />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(sessionRouteCall).toHaveBeenCalledTimes(1)
|
||||
|
||||
const session1 = screen.getByTestId("session-consumer-1").textContent
|
||||
const session2 = screen.getByTestId("session-consumer-2").textContent
|
||||
|
||||
expect(session1).toEqual(session2)
|
||||
})
|
||||
})
|
||||
|
||||
function ProviderFlow({ options = {} }) {
|
||||
return (
|
||||
<>
|
||||
<Provider options={options}>
|
||||
<SessionConsumer />
|
||||
<SessionConsumer testId="2" />
|
||||
</Provider>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function SessionConsumer({ testId = 1 }) {
|
||||
const [session, loading] = useSession()
|
||||
|
||||
if (loading) return <span>loading</span>
|
||||
|
||||
return (
|
||||
<div data-testid={`session-consumer-${testId}`}>
|
||||
{JSON.stringify(session)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
105
src/client/__tests__/csrf.test.js
Normal file
105
src/client/__tests__/csrf.test.js
Normal file
@@ -0,0 +1,105 @@
|
||||
import { useState } from "react"
|
||||
import userEvent from "@testing-library/user-event"
|
||||
import { render, screen, waitFor } from "@testing-library/react"
|
||||
import { server, mockCSRFToken } from "./helpers/mocks"
|
||||
import logger from "../../lib/logger"
|
||||
import { getCsrfToken } from ".."
|
||||
import { rest } from "msw"
|
||||
|
||||
jest.mock("../../lib/logger", () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
warn: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
},
|
||||
proxyLogger(logger) {
|
||||
return logger
|
||||
},
|
||||
}))
|
||||
|
||||
beforeAll(() => {
|
||||
server.listen()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers()
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
server.close()
|
||||
})
|
||||
|
||||
test("returns the Cross Site Request Forgery Token (CSRF Token) required to make POST requests", async () => {
|
||||
render(<CSRFFlow />)
|
||||
|
||||
userEvent.click(screen.getByRole("button"))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("csrf-result").textContent).toEqual(
|
||||
mockCSRFToken.csrfToken
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test("when there's no CSRF token returned, it'll reflect that", async () => {
|
||||
server.use(
|
||||
rest.get("/api/auth/csrf", (req, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
...mockCSRFToken,
|
||||
csrfToken: null,
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
render(<CSRFFlow />)
|
||||
|
||||
userEvent.click(screen.getByRole("button"))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("csrf-result").textContent).toBe("null-response")
|
||||
})
|
||||
})
|
||||
|
||||
test("when the fetch fails it'll throw a client fetch error", async () => {
|
||||
server.use(
|
||||
rest.get("/api/auth/csrf", (req, res, ctx) =>
|
||||
res(ctx.status(500), ctx.text("some error happened"))
|
||||
)
|
||||
)
|
||||
|
||||
render(<CSRFFlow />)
|
||||
|
||||
userEvent.click(screen.getByRole("button"))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(logger.error).toHaveBeenCalledTimes(1)
|
||||
expect(logger.error).toBeCalledWith(
|
||||
"CLIENT_FETCH_ERROR",
|
||||
"csrf",
|
||||
new SyntaxError("Unexpected token s in JSON at position 0")
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
function CSRFFlow() {
|
||||
const [response, setResponse] = useState()
|
||||
|
||||
async function handleCSRF() {
|
||||
const result = await getCsrfToken()
|
||||
setResponse(result)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<p data-testid="csrf-result">
|
||||
{response === null ? "null-response" : response || "no response"}
|
||||
</p>
|
||||
<button onClick={handleCSRF}>Get CSRF</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { rest } from "msw"
|
||||
import { randomBytes } from "crypto"
|
||||
|
||||
export const mockSession = {
|
||||
ok: true,
|
||||
user: {
|
||||
image: null,
|
||||
name: "John",
|
||||
@@ -12,6 +13,7 @@ export const mockSession = {
|
||||
}
|
||||
|
||||
export const mockProviders = {
|
||||
ok: true,
|
||||
github: {
|
||||
id: "github",
|
||||
name: "Github",
|
||||
@@ -34,6 +36,7 @@ export const mockProviders = {
|
||||
}
|
||||
|
||||
export const mockCSRFToken = {
|
||||
ok: true,
|
||||
csrfToken: randomBytes(32).toString("hex"),
|
||||
}
|
||||
|
||||
85
src/client/__tests__/providers.test.js
Normal file
85
src/client/__tests__/providers.test.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import { useState } from "react"
|
||||
import userEvent from "@testing-library/user-event"
|
||||
import { render, screen, waitFor } from "@testing-library/react"
|
||||
import { server, mockProviders } from "./helpers/mocks"
|
||||
import { getProviders } from ".."
|
||||
import logger from "../../lib/logger"
|
||||
import { rest } from "msw"
|
||||
|
||||
jest.mock("../../lib/logger", () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
warn: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
},
|
||||
proxyLogger(logger) {
|
||||
return logger
|
||||
},
|
||||
}))
|
||||
|
||||
beforeAll(() => {
|
||||
server.listen()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers()
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
server.close()
|
||||
})
|
||||
|
||||
test("when called it'll return the currently configured providers for sign in", async () => {
|
||||
render(<ProvidersFlow />)
|
||||
|
||||
userEvent.click(screen.getByRole("button"))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("providers-result").textContent).toEqual(
|
||||
JSON.stringify(mockProviders)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
test("when failing to fetch the providers, it'll log the error", async () => {
|
||||
server.use(
|
||||
rest.get("/api/auth/providers", (req, res, ctx) =>
|
||||
res(ctx.status(500), ctx.text("some error happened"))
|
||||
)
|
||||
)
|
||||
|
||||
render(<ProvidersFlow />)
|
||||
|
||||
userEvent.click(screen.getByRole("button"))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(logger.error).toHaveBeenCalledTimes(1)
|
||||
expect(logger.error).toBeCalledWith(
|
||||
"CLIENT_FETCH_ERROR",
|
||||
"providers",
|
||||
new SyntaxError("Unexpected token s in JSON at position 0")
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
function ProvidersFlow() {
|
||||
const [response, setResponse] = useState()
|
||||
|
||||
async function handleGerProviders() {
|
||||
const result = await getProviders()
|
||||
setResponse(result)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<p data-testid="providers-result">
|
||||
{response === null
|
||||
? "null-response"
|
||||
: JSON.stringify(response) || "no response"}
|
||||
</p>
|
||||
<button onClick={handleGerProviders}>Get Providers</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { render, screen, waitFor } from "@testing-library/react"
|
||||
import { rest } from "msw"
|
||||
import { server, mockSession } from "./mocks"
|
||||
import { server, mockSession } from "./helpers/mocks"
|
||||
import logger from "../../lib/logger"
|
||||
import { useState, useEffect } from "react"
|
||||
import { getSession } from ".."
|
||||
import { getBroadcastEvents } from "./utils"
|
||||
import { getBroadcastEvents } from "./helpers/utils"
|
||||
|
||||
jest.mock("../../lib/logger", () => ({
|
||||
__esModule: true,
|
||||
@@ -27,10 +27,12 @@ beforeEach(() => {
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers()
|
||||
jest.restoreAllMocks()
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
afterAll(() => server.close())
|
||||
afterAll(() => {
|
||||
server.close()
|
||||
})
|
||||
|
||||
test("if it can fetch the session, it should store it in `localStorage`", async () => {
|
||||
render(<SessionFlow />)
|
||||
@@ -81,7 +83,7 @@ function SessionFlow() {
|
||||
useEffect(() => {
|
||||
async function fetchUserSession() {
|
||||
try {
|
||||
const result = await getSession({})
|
||||
const result = await getSession()
|
||||
setSession(result)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
@@ -90,8 +92,7 @@ function SessionFlow() {
|
||||
fetchUserSession()
|
||||
}, [])
|
||||
|
||||
if (session) {
|
||||
return <pre>{JSON.stringify(session, null, 2)}</pre>
|
||||
}
|
||||
if (session) return <pre>{JSON.stringify(session, null, 2)}</pre>
|
||||
|
||||
return <p>No session</p>
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
mockCredentialsResponse,
|
||||
mockEmailResponse,
|
||||
mockGithubResponse,
|
||||
} from "./mocks"
|
||||
} from "./helpers/mocks"
|
||||
import { signIn } from ".."
|
||||
import { rest } from "msw"
|
||||
|
||||
@@ -36,7 +36,7 @@ beforeAll(() => {
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks()
|
||||
jest.clearAllMocks()
|
||||
server.resetHandlers()
|
||||
})
|
||||
|
||||
@@ -284,7 +284,7 @@ function SignInFlow({
|
||||
<p data-testid="signin-result">
|
||||
{response ? JSON.stringify(response) : "no response"}
|
||||
</p>
|
||||
<button onClick={() => handleSignIn()}>Sign in</button>
|
||||
<button onClick={handleSignIn}>Sign in</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useState } from "react"
|
||||
import userEvent from "@testing-library/user-event"
|
||||
import { render, screen, waitFor } from "@testing-library/react"
|
||||
import { server, mockSignOutResponse } from "./mocks"
|
||||
import { server, mockSignOutResponse } from "./helpers/mocks"
|
||||
import { signOut } from ".."
|
||||
import { rest } from "msw"
|
||||
import { getBroadcastEvents } from "./utils"
|
||||
import { getBroadcastEvents } from "./helpers/utils"
|
||||
|
||||
const { location } = window
|
||||
|
||||
@@ -24,7 +24,7 @@ beforeEach(() => {
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks()
|
||||
jest.clearAllMocks()
|
||||
server.resetHandlers()
|
||||
})
|
||||
|
||||
@@ -113,7 +113,7 @@ test("will broadcast the signout event to other tabs", async () => {
|
||||
function SignOutFlow({ callbackUrl, redirect = true }) {
|
||||
const [response, setResponse] = useState(null)
|
||||
|
||||
async function setSignOutRes() {
|
||||
async function handleSignOut() {
|
||||
const result = await signOut({ callbackUrl, redirect })
|
||||
setResponse(result)
|
||||
}
|
||||
@@ -123,7 +123,7 @@ function SignOutFlow({ callbackUrl, redirect = true }) {
|
||||
<p data-testid="signout-result">
|
||||
{response ? JSON.stringify(response) : "no response"}
|
||||
</p>
|
||||
<button onClick={() => setSignOutRes()}>Sign out</button>
|
||||
<button onClick={handleSignOut}>Sign out</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
18
src/providers/naver.js
Normal file
18
src/providers/naver.js
Normal file
@@ -0,0 +1,18 @@
|
||||
export default function Naver(options) {
|
||||
return {
|
||||
id: "naver",
|
||||
name: "Naver",
|
||||
type: "oauth",
|
||||
version: "2.0",
|
||||
params: { grant_type: "authorization_code" },
|
||||
protection: ["state"],
|
||||
accessTokenUrl: "https://nid.naver.com/oauth2.0/token",
|
||||
authorizationUrl:
|
||||
"https://nid.naver.com/oauth2.0/authorize?response_type=code",
|
||||
profileUrl: "https://openapi.naver.com/v1/nid/me",
|
||||
profile(profile) {
|
||||
return profile.response
|
||||
},
|
||||
...options,
|
||||
}
|
||||
}
|
||||
15
types/providers.d.ts
vendored
15
types/providers.d.ts
vendored
@@ -1,5 +1,6 @@
|
||||
import { Profile, TokenSet, User } from "."
|
||||
import { Awaitable, NextApiRequest } from "./internals/utils"
|
||||
import { Options as SMTPConnectionOptions } from 'nodemailer/lib/smtp-connection'
|
||||
|
||||
export type ProviderType = "oauth" | "email" | "credentials"
|
||||
|
||||
@@ -83,6 +84,7 @@ export type OAuthProviderType =
|
||||
| "Mailchimp"
|
||||
| "MailRu"
|
||||
| "Medium"
|
||||
| "Naver"
|
||||
| "Netlify"
|
||||
| "Okta"
|
||||
| "Osso"
|
||||
@@ -126,17 +128,6 @@ export type CredentialsProvider = (
|
||||
|
||||
export type CredentialsProviderType = "Credentials"
|
||||
|
||||
/** Email Provider */
|
||||
|
||||
export interface EmailConfigServerOptions {
|
||||
host: string
|
||||
port: number
|
||||
auth: {
|
||||
user: string
|
||||
pass: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SendVerificationRequest = (params: {
|
||||
identifier: string
|
||||
url: string
|
||||
@@ -148,7 +139,7 @@ export type SendVerificationRequest = (params: {
|
||||
export interface EmailConfig extends CommonProviderOptions {
|
||||
type: "email"
|
||||
// TODO: Make use of https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html
|
||||
server: string | EmailConfigServerOptions
|
||||
server: string | SMTPConnectionOptions
|
||||
/** @default "NextAuth <no-reply@example.com>" */
|
||||
from?: string
|
||||
/**
|
||||
|
||||
@@ -13,10 +13,10 @@ You can find the full schema in the table structure section below.
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Install `next-auth` and `@next-auth/dynamodb-adapter@canary`
|
||||
1. Install `next-auth` and `@next-auth/dynamodb-adapter`
|
||||
|
||||
```js
|
||||
npm install next-auth @next-auth/dynamodb-adapter@canary
|
||||
npm install next-auth @next-auth/dynamodb-adapter
|
||||
```
|
||||
|
||||
2. Add this adapter to your `pages/api/auth/[...nextauth].js` next-auth configuration object.
|
||||
|
||||
@@ -11,10 +11,10 @@ You can find the Fauna schema and seed information in the docs at [next-auth.js.
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Install `next-auth` and `@next-auth/fauna-adapter@canary`
|
||||
1. Install `next-auth` and `@next-auth/fauna-adapter`
|
||||
|
||||
```js
|
||||
npm install next-auth @next-auth/fauna-adapter@canary
|
||||
npm install next-auth @next-auth/fauna-adapter
|
||||
```
|
||||
|
||||
2. Add this adapter to your `pages/api/[...nextauth].js` next-auth configuration object.
|
||||
|
||||
@@ -9,10 +9,10 @@ This is the Firebase Adapter for [`next-auth`](https://next-auth.js.org). This p
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Install `next-auth` and `@next-auth/firebase-adapter@canary`
|
||||
1. Install `next-auth` and `@next-auth/firebase-adapter`
|
||||
|
||||
```js
|
||||
npm install next-auth @next-auth/firebase-adapter@canary
|
||||
npm install next-auth @next-auth/firebase-adapter
|
||||
```
|
||||
|
||||
2. Add this adapter to your `pages/api/[...nextauth].js` next-auth configuration object.
|
||||
|
||||
@@ -13,10 +13,10 @@ Depending on your architecture you can use PouchDB's http adapter to reach any d
|
||||
|
||||
> **Prerequesite**: Your PouchDB instance MUST provide the `pouchdb-find` plugin since it is used internally by the adapter to build and manage indexes
|
||||
|
||||
1. Install `next-auth` and `@next-auth/pouchdb-adapter@canary`
|
||||
1. Install `next-auth` and `@next-auth/pouchdb-adapter`
|
||||
|
||||
```js
|
||||
npm install next-auth @next-auth/pouchdb-adapter@canary
|
||||
npm install next-auth @next-auth/pouchdb-adapter
|
||||
```
|
||||
|
||||
2. Add this adapter to your `pages/api/auth/[...nextauth].js` next-auth configuration object
|
||||
@@ -28,10 +28,9 @@ import { PouchDBAdapter } from "@next-auth/pouchdb-adapter"
|
||||
import PouchDB from "pouchdb"
|
||||
|
||||
// Setup your PouchDB instance and database
|
||||
PouchDB
|
||||
.plugin(require("pouchdb-adapter-leveldb")) // Any other adapter
|
||||
.plugin(require("pouchdb-find")) // Don't forget the `pouchdb-find` plugin
|
||||
|
||||
PouchDB.plugin(require("pouchdb-adapter-leveldb")) // Any other adapter
|
||||
.plugin(require("pouchdb-find")) // Don't forget the `pouchdb-find` plugin
|
||||
|
||||
const pouchdb = new PouchDB("auth_db", { adapter: "leveldb" })
|
||||
|
||||
// For more information on each option (and a full list of options) go to
|
||||
@@ -49,7 +48,7 @@ export default NextAuth({
|
||||
})
|
||||
```
|
||||
|
||||
## Advanced
|
||||
## Advanced
|
||||
|
||||
### Memory-First Caching Strategy
|
||||
|
||||
@@ -60,4 +59,3 @@ Use an in-memory PouchDB as your main authentication database, and synchronize i
|
||||
This will most likely not increase performance much in a serverless environment due to various reasons such as concurrency, function startup time increases, etc.
|
||||
|
||||
For more details, please see https://pouchdb.com/api.html#sync
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@ You can also use NextAuth.js with the new experimental Adapter for [Prisma](http
|
||||
You may have noticed there is a `prisma` and `prisma-legacy` adapter. This is due to historical reasons, but the code has mostly converged so that there is no longer much difference between the two. The legacy adapter, however, does have the ability to rename tables which the newer version does not.
|
||||
:::
|
||||
|
||||
To use this Adapter, you need to install Prisma Client, Prisma CLI, and the separate `@next-auth/prisma-adapter@canary` package:
|
||||
To use this Adapter, you need to install Prisma Client, Prisma CLI, and the separate `@next-auth/prisma-adapter` package:
|
||||
|
||||
```
|
||||
npm install @prisma/client @next-auth/prisma-adapter@canary
|
||||
npm install @prisma/client @next-auth/prisma-adapter
|
||||
npm install prisma --save-dev
|
||||
```
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@ You can use the [session callback](/configuration/callbacks#session-callback) to
|
||||
|
||||
## useSession()
|
||||
|
||||
* Client Side: **Yes**
|
||||
* Server Side: No
|
||||
- Client Side: **Yes**
|
||||
- Server Side: No
|
||||
|
||||
The `useSession()` React Hook in the NextAuth.js client is the easiest way to check if someone is signed in.
|
||||
|
||||
@@ -39,12 +39,12 @@ It works best when the [`<Provider>`](#provider) is added to `pages/_app.js`.
|
||||
#### Example
|
||||
|
||||
```jsx
|
||||
import { useSession } from 'next-auth/client'
|
||||
import { useSession } from "next-auth/client"
|
||||
|
||||
export default function Component() {
|
||||
const [ session, loading ] = useSession()
|
||||
const [session, loading] = useSession()
|
||||
|
||||
if(session) {
|
||||
if (session) {
|
||||
return <p>Signed in as {session.user.email}</p>
|
||||
}
|
||||
|
||||
@@ -56,8 +56,8 @@ export default function Component() {
|
||||
|
||||
## getSession()
|
||||
|
||||
* Client Side: **Yes**
|
||||
* Server Side: **Yes**
|
||||
- Client Side: **Yes**
|
||||
- Server Side: **Yes**
|
||||
|
||||
NextAuth.js provides a `getSession()` method which can be called client or server side to return a session.
|
||||
|
||||
@@ -75,7 +75,7 @@ async function myFunction() {
|
||||
#### Server Side Example
|
||||
|
||||
```js
|
||||
import { getSession } from 'next-auth/client'
|
||||
import { getSession } from "next-auth/client"
|
||||
|
||||
export default async (req, res) => {
|
||||
const session = await getSession({ req })
|
||||
@@ -94,8 +94,8 @@ The tutorial [securing pages and API routes](/tutorials/securing-pages-and-api-r
|
||||
|
||||
## getCsrfToken()
|
||||
|
||||
* Client Side: **Yes**
|
||||
* Server Side: **Yes**
|
||||
- Client Side: **Yes**
|
||||
- Server Side: **Yes**
|
||||
|
||||
The `getCsrfToken()` method returns the current Cross Site Request Forgery Token (CSRF Token) required to make POST requests (e.g. for signing in and signing out).
|
||||
|
||||
@@ -113,7 +113,7 @@ async function myFunction() {
|
||||
#### Server Side Example
|
||||
|
||||
```js
|
||||
import { getCsrfToken } from 'next-auth/client'
|
||||
import { getCsrfToken } from "next-auth/client"
|
||||
|
||||
export default async (req, res) => {
|
||||
const csrfToken = await getCsrfToken({ req })
|
||||
@@ -126,8 +126,8 @@ export default async (req, res) => {
|
||||
|
||||
## getProviders()
|
||||
|
||||
* Client Side: **Yes**
|
||||
* Server Side: **Yes**
|
||||
- Client Side: **Yes**
|
||||
- Server Side: **Yes**
|
||||
|
||||
The `getProviders()` method returns the list of providers currently configured for sign in.
|
||||
|
||||
@@ -140,11 +140,11 @@ It can be useful if you are creating a dynamic custom sign in page.
|
||||
#### API Route
|
||||
|
||||
```jsx title="pages/api/example.js"
|
||||
import { getProviders } from 'next-auth/client'
|
||||
import { getProviders } from "next-auth/client"
|
||||
|
||||
export default async (req, res) => {
|
||||
const providers = await getProviders()
|
||||
console.log('Providers', providers)
|
||||
console.log("Providers", providers)
|
||||
res.end()
|
||||
}
|
||||
```
|
||||
@@ -157,8 +157,8 @@ Unlike `getSession()` and `getCsrfToken()`, when calling `getProviders()` server
|
||||
|
||||
## signIn()
|
||||
|
||||
* Client Side: **Yes**
|
||||
* Server Side: No
|
||||
- Client Side: **Yes**
|
||||
- Server Side: No
|
||||
|
||||
Using the `signIn()` method ensures the user ends back on the page they started on after completing a sign in flow. It will also handle CSRF Tokens for you automatically when signing in with email.
|
||||
|
||||
@@ -167,20 +167,18 @@ The `signIn()` method can be called from the client in different ways, as shown
|
||||
#### Redirects to sign in page when clicked
|
||||
|
||||
```js
|
||||
import { signIn } from 'next-auth/client'
|
||||
import { signIn } from "next-auth/client"
|
||||
|
||||
export default () => (
|
||||
<button onClick={() => signIn()}>Sign in</button>
|
||||
)
|
||||
export default () => <button onClick={() => signIn()}>Sign in</button>
|
||||
```
|
||||
|
||||
#### Starts Google OAuth sign-in flow when clicked
|
||||
|
||||
```js
|
||||
import { signIn } from 'next-auth/client'
|
||||
import { signIn } from "next-auth/client"
|
||||
|
||||
export default () => (
|
||||
<button onClick={() => signIn('google')}>Sign in with Google</button>
|
||||
<button onClick={() => signIn("google")}>Sign in with Google</button>
|
||||
)
|
||||
```
|
||||
|
||||
@@ -189,10 +187,10 @@ export default () => (
|
||||
When using it with the email flow, pass the target `email` as an option.
|
||||
|
||||
```js
|
||||
import { signIn } from 'next-auth/client'
|
||||
import { signIn } from "next-auth/client"
|
||||
|
||||
export default ({ email }) => (
|
||||
<button onClick={() => signIn('email', { email })}>Sign in with Email</button>
|
||||
<button onClick={() => signIn("email", { email })}>Sign in with Email</button>
|
||||
)
|
||||
```
|
||||
|
||||
@@ -204,9 +202,9 @@ You can specify a different `callbackUrl` by specifying it as the second argumen
|
||||
|
||||
e.g.
|
||||
|
||||
* `signIn(null, { callbackUrl: 'http://localhost:3000/foo' })`
|
||||
* `signIn('google', { callbackUrl: 'http://localhost:3000/foo' })`
|
||||
* `signIn('email', { email, callbackUrl: 'http://localhost:3000/foo' })`
|
||||
- `signIn(null, { callbackUrl: 'http://localhost:3000/foo' })`
|
||||
- `signIn('google', { callbackUrl: 'http://localhost:3000/foo' })`
|
||||
- `signIn('email', { email, callbackUrl: 'http://localhost:3000/foo' })`
|
||||
|
||||
The URL must be considered valid by the [redirect callback handler](/configuration/callbacks#redirect-callback). By default it requires the URL to be an absolute URL at the same hostname, or else it will redirect to the homepage. You can define your own [redirect callback](/configuration/callbacks#redirect-callback) to allow other URLs, including supporting relative URLs.
|
||||
|
||||
@@ -234,8 +232,8 @@ e.g.
|
||||
error: string | undefined
|
||||
/**
|
||||
* HTTP status code,
|
||||
* hints the kind of error that happened.
|
||||
*/
|
||||
* hints the kind of error that happened.
|
||||
*/
|
||||
status: number
|
||||
/**
|
||||
* `true` if the signin was successful
|
||||
@@ -258,8 +256,8 @@ See the [Authorization Request OIDC spec](https://openid.net/specs/openid-connec
|
||||
|
||||
e.g.
|
||||
|
||||
* `signIn("identity-server4", null, { prompt: "login" })` *always ask the user to reauthenticate*
|
||||
* `signIn("auth0", null, { login_hint: "info@example.com" })` *hints the e-mail address to the provider*
|
||||
- `signIn("identity-server4", null, { prompt: "login" })` _always ask the user to reauthenticate_
|
||||
- `signIn("auth0", null, { login_hint: "info@example.com" })` _hints the e-mail address to the provider_
|
||||
|
||||
:::note
|
||||
You can also set these parameters through [`provider.authorizationParams`](/configuration/providers#oauth-provider-options).
|
||||
@@ -273,19 +271,17 @@ The following parameters are always overridden server-side: `redirect_uri`, `sta
|
||||
|
||||
## signOut()
|
||||
|
||||
* Client Side: **Yes**
|
||||
* Server Side: No
|
||||
- Client Side: **Yes**
|
||||
- Server Side: No
|
||||
|
||||
Using the `signOut()` method ensures the user ends back on the page they started on after completing the sign out flow. It also handles CSRF tokens for you automatically.
|
||||
|
||||
It reloads the page in the browser when complete.
|
||||
|
||||
```js
|
||||
import { signOut } from 'next-auth/client'
|
||||
import { signOut } from "next-auth/client"
|
||||
|
||||
export default () => (
|
||||
<button onClick={() => signOut()}>Sign out</button>
|
||||
)
|
||||
export default () => <button onClick={() => signOut()}>Sign out</button>
|
||||
```
|
||||
|
||||
#### Specifying a callbackUrl
|
||||
@@ -315,9 +311,9 @@ Using the supplied React `<Provider>` allows instances of `useSession()` to shar
|
||||
This improves performance, reduces network calls and avoids page flicker when rendering. It is highly recommended and can be easily added to all pages in Next.js apps by using `pages/_app.js`.
|
||||
|
||||
```jsx title="pages/_app.js"
|
||||
import { Provider } from 'next-auth/client'
|
||||
import { Provider } from "next-auth/client"
|
||||
|
||||
export default function App ({ Component, pageProps }) {
|
||||
export default function App({ Component, pageProps }) {
|
||||
return (
|
||||
<Provider session={pageProps.session}>
|
||||
<Component {...pageProps} />
|
||||
@@ -360,8 +356,8 @@ import { Provider } from 'next-auth/client'
|
||||
export default function App ({ Component, pageProps }) {
|
||||
return (
|
||||
<Provider session={pageProps.session}
|
||||
options={{
|
||||
clientMaxAge: 60 // Re-fetch session if cache is older than 60 seconds
|
||||
options={{
|
||||
clientMaxAge: 60, // Re-fetch session if cache is older than 60 seconds
|
||||
keepAlive: 5 * 60 // Send keepAlive message every 5 minutes
|
||||
}}
|
||||
>
|
||||
@@ -376,7 +372,7 @@ export default function App ({ Component, pageProps }) {
|
||||
|
||||
Every tab/window maintains its own copy of the local session state; the session is not stored in shared storage like localStorage or sessionStorage. Any update in one tab/window triggers a message to other tabs/windows to update their own session state.
|
||||
|
||||
Using low values for `clientMaxAge` or `keepAlive` will increase network traffic and load on authenticated clients and may impact hosting costs and performance.
|
||||
Using low values for `clientMaxAge` or `keepAlive` will increase network traffic and load on authenticated clients and may impact hosting costs and performance.
|
||||
:::
|
||||
|
||||
#### Client Max Age
|
||||
@@ -402,7 +398,7 @@ If set to any value other than zero, it specifies in seconds how often the clien
|
||||
The value for `keepAlive` should always be lower than the value of the session `maxAge` option.
|
||||
|
||||
:::note
|
||||
See [**the Next.js documentation**](https://nextjs.org/docs/advanced-features/custom-app) for more information on **_app.js** in Next.js applications.
|
||||
See [**the Next.js documentation**](https://nextjs.org/docs/advanced-features/custom-app) for more information on **\_app.js** in Next.js applications.
|
||||
:::
|
||||
|
||||
## Alternatives
|
||||
@@ -412,8 +408,8 @@ See [**the Next.js documentation**](https://nextjs.org/docs/advanced-features/cu
|
||||
Due to the way Next.js handles `getServerSideProps` / `getInitialProps`, every protected page load has to make a server-side query to check if the session is valid and then generate the requested page. This alternative solution allows for showing a loading state on the initial check and every page transition afterward will be client-side, without having to check with the server and regenerate pages.
|
||||
|
||||
```js title="pages/admin.jsx"
|
||||
export default function AdminDashboard () {
|
||||
const [session] = useSession()
|
||||
export default function AdminDashboard() {
|
||||
const [session] = useSession()
|
||||
// session is always non-null inside this page, all the way down the React tree.
|
||||
return "Some super secret dashboard"
|
||||
}
|
||||
@@ -424,12 +420,15 @@ AdminDashboard.auth = true
|
||||
```jsx title="pages/_app.jsx"
|
||||
export default function App({ Component, pageProps }) {
|
||||
return (
|
||||
<SessionProvider session={pageProps.session}>
|
||||
{Component.auth
|
||||
? <Auth><Component {...pageProps} /></Auth>
|
||||
: <Component {...pageProps} />
|
||||
}
|
||||
</SessionProvider>
|
||||
<Provider session={pageProps.session}>
|
||||
{Component.auth ? (
|
||||
<Auth>
|
||||
<Component {...pageProps} />
|
||||
</Auth>
|
||||
) : (
|
||||
<Component {...pageProps} />
|
||||
)}
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -444,7 +443,7 @@ function Auth({ children }) {
|
||||
if (isUser) {
|
||||
return children
|
||||
}
|
||||
|
||||
|
||||
// Session is being fetched, or no user.
|
||||
// If no user, useEffect() will redirect.
|
||||
return <div>Loading...</div>
|
||||
@@ -456,20 +455,19 @@ It can be easily be extended/modified to support something like an options objec
|
||||
```jsx title="pages/admin.jsx"
|
||||
AdminDashboard.auth = {
|
||||
role: "admin",
|
||||
loading: <AdminLoadingSkeleton/>,
|
||||
unauthorized: "/login-with-different-user" // redirect to this url
|
||||
loading: <AdminLoadingSkeleton />,
|
||||
unauthorized: "/login-with-different-user", // redirect to this url
|
||||
}
|
||||
```
|
||||
|
||||
Because of how _app is done, it won't unnecessarily contact the /api/auth/session endpoint for pages that do not require auth.
|
||||
Because of how \_app is done, it won't unnecessarily contact the /api/auth/session endpoint for pages that do not require auth.
|
||||
|
||||
More information can be found in the following [Github Issue](https://github.com/nextauthjs/next-auth/issues/1210).
|
||||
|
||||
### NextAuth.js + React-Query
|
||||
|
||||
There is also an alternative client-side API library based upon [`react-query`](https://www.npmjs.com/package/react-query) available under [`nextauthjs/react-query`](https://github.com/nextauthjs/react-query).
|
||||
There is also an alternative client-side API library based upon [`react-query`](https://www.npmjs.com/package/react-query) available under [`nextauthjs/react-query`](https://github.com/nextauthjs/react-query).
|
||||
|
||||
If you use `react-query` in your project already, you can leverage it with NextAuth.js to handle the client-side session management for you as well. This replaces NextAuth.js's native `useSession` and `Provider` from `next-auth/client`.
|
||||
|
||||
See repository [`README`](https://github.com/nextauthjs/react-query) for more details.
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ The Email provider can be used in conjunction with (or instead of) one or more O
|
||||
|
||||
### How it works
|
||||
|
||||
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 with that time (i.e. by clicking on the link in the email) an account is created for the user and they are signed in.
|
||||
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.
|
||||
|
||||
If someone provides the email address of an _existing account_ when signing in, an email is sent and they are signed into the account associated with that email address when they follow the link in the email.
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ providers: [
|
||||
```
|
||||
|
||||
:::warning
|
||||
Google only provide the Refresh Token to an application the first time a user signs in.
|
||||
Google only provides Refresh Token to an application the first time a user signs in.
|
||||
|
||||
To force Google to re-issue a Refresh Token, the user needs to remove the application from their account and sign in again:
|
||||
https://myaccount.google.com/permissions
|
||||
|
||||
34
www/docs/providers/naver.md
Normal file
34
www/docs/providers/naver.md
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
id: naver
|
||||
title: Naver
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
https://developers.naver.com/docs/login/overview/overview.md
|
||||
|
||||
## Configuration
|
||||
|
||||
https://developers.naver.com/docs/login/api/api.md
|
||||
|
||||
## Options
|
||||
|
||||
The **Naver Provider** comes with a set of default options:
|
||||
|
||||
- [Naver Provider options](https://github.com/nextauthjs/next-auth/blob/main/src/providers/naver.js)
|
||||
|
||||
You can override any of the options to suit your own use case.
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
import Providers from `next-auth/providers`
|
||||
...
|
||||
providers: [
|
||||
Providers.Naver({
|
||||
clientId: process.env.NAVER_CLIENT_ID,
|
||||
clientSecret: process.env.NAVER_CLIENT_SECRET
|
||||
})
|
||||
]
|
||||
...
|
||||
```
|
||||
@@ -48,7 +48,7 @@ export default {
|
||||
```
|
||||
|
||||
:::note
|
||||
[View source for built-in TypeORM models and schemas](https://github.com/nextauthjs/adapters/tree/canary/packages/typeorm-legacy/src/models)
|
||||
[View source for built-in TypeORM models and schemas](https://github.com/nextauthjs/adapters/tree/main/packages/typeorm-legacy/src/models)
|
||||
:::
|
||||
|
||||
## Using custom models
|
||||
|
||||
@@ -42,12 +42,12 @@ module.exports = {
|
||||
position: "left",
|
||||
},
|
||||
{
|
||||
href: "https://www.npmjs.com/package/next-auth",
|
||||
to: "https://www.npmjs.com/package/next-auth",
|
||||
label: "npm",
|
||||
position: "right",
|
||||
},
|
||||
{
|
||||
href: "https://github.com/nextauthjs/next-auth",
|
||||
to: "https://github.com/nextauthjs/next-auth",
|
||||
label: "GitHub",
|
||||
position: "right",
|
||||
},
|
||||
@@ -73,8 +73,8 @@ module.exports = {
|
||||
to: "/contributors",
|
||||
},
|
||||
{
|
||||
label: "Canary documentation",
|
||||
to: "https://next-auth-git-canary.nextauthjs.vercel.app/",
|
||||
label: "Next documentation",
|
||||
to: "https://next-auth-git-next.nextauthjs.vercel.app",
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -118,6 +118,13 @@ module.exports = {
|
||||
],
|
||||
copyright: "NextAuth.js © Iain Collins 2021",
|
||||
},
|
||||
colorMode: {
|
||||
respectPrefersColorScheme: true,
|
||||
switchConfig: {
|
||||
darkIcon: "🌑",
|
||||
lightIcon: "💡",
|
||||
},
|
||||
},
|
||||
},
|
||||
presets: [
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user