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 | |
|---|---|---|---|
|
|
5a89ab69d3 | ||
|
|
665445818e | ||
|
|
67cf2a11bb | ||
|
|
832d51f10e | ||
|
|
29862ac887 | ||
|
|
5aa2b61b88 | ||
|
|
929c644653 | ||
|
|
2657e72e81 | ||
|
|
8ff7dbb18f | ||
|
|
748d576a5a | ||
|
|
9f16e3f0fb | ||
|
|
1042e9a93d | ||
|
|
aa57f2dd7e | ||
|
|
1817286ce3 | ||
|
|
b942dd34f3 | ||
|
|
4d9622e1cc | ||
|
|
a7eadf80e5 | ||
|
|
75c7dbc3e7 |
7
.github/workflows/release.yml
vendored
7
.github/workflows/release.yml
vendored
@@ -21,7 +21,12 @@ jobs:
|
||||
- name: Dependencies
|
||||
uses: bahmutov/npm-install@v1
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
run: npm test -- --coverage --verbose
|
||||
- name: Coverage
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
directory: ./coverage
|
||||
fail_ci_if_error: false
|
||||
- name: Build
|
||||
run: npm run build
|
||||
release:
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -58,4 +58,7 @@ app/yarn.lock
|
||||
/_work
|
||||
|
||||
# Prisma migrations
|
||||
/prisma/migrations
|
||||
/prisma/migrations
|
||||
|
||||
# Tests
|
||||
/coverage
|
||||
@@ -95,7 +95,7 @@ The databases can take a few seconds to start up, so you might need to give it a
|
||||
|
||||
## For maintainers
|
||||
|
||||
We use [semantic-release](https://github.com/semantic-release/semantic-release) together with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0) to automate releases. This makes the maintainenance process easier and less error-prone to human error. Please study the "Conventional Commits" site to understand how to write a good commit message.
|
||||
We use [semantic-release](https://github.com/semantic-release/semantic-release) together with [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0) to automate releases. This makes the maintenance process easier and less error-prone to human error. Please study the "Conventional Commits" site to understand how to write a good commit message.
|
||||
|
||||
When accepting Pull Requests, make sure the following:
|
||||
|
||||
@@ -113,9 +113,9 @@ type(scope): title
|
||||
body
|
||||
```
|
||||
|
||||
Scope is the part that will help groupping the different commit types in the release notes.
|
||||
Scope is the part that will help grouping the different commit types in the release notes.
|
||||
|
||||
Some recommened scopes are:
|
||||
Some recommended scopes are:
|
||||
|
||||
- **provider** - Provider related changes. (eg.: "feat(provider): add X provider", "docs(provider): fix typo in X documentation"
|
||||
- **adapter** - Adapter related changes. (eg.: "feat(adapter): add X provider", "docs(provider): fix typo in X documentation"
|
||||
|
||||
@@ -81,7 +81,7 @@ Advanced options allow you to define your own routines to handle controlling wha
|
||||
|
||||
### TypeScript
|
||||
|
||||
NextAuth.js comes with built-in types. For more information and usage, check out the [TypeScript section](https://next-auth.js.org/getting-started/typescript) in the documentaion.
|
||||
NextAuth.js comes with built-in types. For more information and usage, check out the [TypeScript section](https://next-auth.js.org/getting-started/typescript) in the documentation.
|
||||
|
||||
The package at `@types/next-auth` is now deprecated.
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ If you contact us regarding a serious issue:
|
||||
* We will endeavor to get back to you within 72 hours.
|
||||
* We will aim to publish a fix within 30 days.
|
||||
* We will disclose the issue (and credit you, with your consent) once a fix to resolve the issue has been released.
|
||||
* If 90 days has elapsed and we still don't have a fix, we will disclose the issue publically.
|
||||
* If 90 days has elapsed and we still don't have a fix, we will disclose the issue publicly.
|
||||
|
||||
Currently, the best way to report an issue is by emailing me@iaincollins.com
|
||||
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
24
src/providers/coinbase.js
Normal file
24
src/providers/coinbase.js
Normal file
@@ -0,0 +1,24 @@
|
||||
export default function Coinbase(options) {
|
||||
return {
|
||||
id: "coinbase",
|
||||
name: "Coinbase",
|
||||
type: "oauth",
|
||||
version: "2.0",
|
||||
scope: "wallet:user:email wallet:user:read",
|
||||
params: { grant_type: "authorization_code" },
|
||||
accessTokenUrl: "https://api.coinbase.com/oauth/token",
|
||||
requestTokenUrl: "https://api.coinbase.com/oauth/token",
|
||||
authorizationUrl:
|
||||
"https://www.coinbase.com/oauth/authorize?response_type=code",
|
||||
profileUrl: "https://api.coinbase.com/v2/user",
|
||||
profile(profile) {
|
||||
return {
|
||||
id: profile.data.id,
|
||||
name: profile.data.name,
|
||||
email: profile.data.email,
|
||||
image: profile.data.avatar_url,
|
||||
}
|
||||
},
|
||||
...options,
|
||||
}
|
||||
}
|
||||
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,
|
||||
}
|
||||
}
|
||||
2
types/providers.d.ts
vendored
2
types/providers.d.ts
vendored
@@ -63,6 +63,7 @@ export type OAuthProviderType =
|
||||
| "Box"
|
||||
| "Bungie"
|
||||
| "Cognito"
|
||||
| "Coinbase"
|
||||
| "Discord"
|
||||
| "Dropbox"
|
||||
| "EVEOnline"
|
||||
@@ -82,6 +83,7 @@ export type OAuthProviderType =
|
||||
| "Mailchimp"
|
||||
| "MailRu"
|
||||
| "Medium"
|
||||
| "Naver"
|
||||
| "Netlify"
|
||||
| "Okta"
|
||||
| "Osso"
|
||||
|
||||
@@ -19,9 +19,10 @@ You can find the full schema in the table structure section below.
|
||||
npm install next-auth @next-auth/dynamodb-adapter@canary
|
||||
```
|
||||
|
||||
2. Add this adapter to your `pages/api/[...nextauth].js` next-auth configuration object.
|
||||
2. Add this adapter to your `pages/api/auth/[...nextauth].js` next-auth configuration object.
|
||||
|
||||
You need to pass `aws-sdk` to the adapter in addition to the table name.
|
||||
You need to pass `DocumentClient` instance from `aws-sdk` to the adapter.
|
||||
The default table name is `next-auth`, but you can customise that by passing `{ tableName: 'your-table-name' }` as the second parameter in the adapter.
|
||||
|
||||
```javascript title="pages/api/auth/[...nextauth].js"
|
||||
import AWS from "aws-sdk";
|
||||
@@ -48,10 +49,9 @@ export default NextAuth({
|
||||
}),
|
||||
// ...add more providers here
|
||||
],
|
||||
adapter: DynamoDBAdapter({
|
||||
AWS,
|
||||
tableName: "next-auth-test",
|
||||
}),
|
||||
adapter: DynamoDBAdapter(
|
||||
new AWS.DynamoDB.DocumentClient()
|
||||
),
|
||||
...
|
||||
});
|
||||
```
|
||||
@@ -63,7 +63,7 @@ export default NextAuth({
|
||||
The table respects the single table design pattern. This has many advantages:
|
||||
|
||||
- Only one table to manage, monitor and provision.
|
||||
- Querying relations is faster than with multi-table schemas (for eg. retreiving all sessions for a user).
|
||||
- Querying relations is faster than with multi-table schemas (for eg. retrieving all sessions for a user).
|
||||
- Only one table needs to be replicated, if you want to go multi-region.
|
||||
|
||||
Here is a schema of the table :
|
||||
|
||||
63
www/docs/adapters/pouchdb.md
Normal file
63
www/docs/adapters/pouchdb.md
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
id: pouchdb
|
||||
title: PouchDB Adapter
|
||||
---
|
||||
|
||||
# PouchDB
|
||||
|
||||
This is the PouchDB Adapter for [`next-auth`](https://next-auth.js.org). This package can only be used in conjunction with the primary `next-auth` package. It is not a standalone package.
|
||||
|
||||
Depending on your architecture you can use PouchDB's http adapter to reach any database compliant with the CouchDB protocol (CouchDB, Cloudant, ...) or use any other PouchDB compatible adapter (leveldb, in-memory, ...)
|
||||
|
||||
## Getting Started
|
||||
|
||||
> **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`
|
||||
|
||||
```js
|
||||
npm install next-auth @next-auth/pouchdb-adapter@canary
|
||||
```
|
||||
|
||||
2. Add this adapter to your `pages/api/auth/[...nextauth].js` next-auth configuration object
|
||||
|
||||
```javascript title="pages/api/auth/[...nextauth].js"
|
||||
import NextAuth from "next-auth"
|
||||
import Providers from "next-auth/providers"
|
||||
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
|
||||
|
||||
const pouchdb = new PouchDB("auth_db", { adapter: "leveldb" })
|
||||
|
||||
// For more information on each option (and a full list of options) go to
|
||||
// https://next-auth.js.org/configuration/options
|
||||
export default NextAuth({
|
||||
// https://next-auth.js.org/configuration/providers
|
||||
providers: [
|
||||
Providers.Google({
|
||||
clientId: process.env.GOOGLE_ID,
|
||||
clientSecret: process.env.GOOGLE_SECRET,
|
||||
}),
|
||||
],
|
||||
adapter: PouchDBAdapter(pouchdb),
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
## Advanced
|
||||
|
||||
### Memory-First Caching Strategy
|
||||
|
||||
If you need to boost your authentication layer performance, you may use PouchDB's powerful sync features and various adapters, to build a memory-first caching strategy.
|
||||
|
||||
Use an in-memory PouchDB as your main authentication database, and synchronize it with any other persisted PouchDB. You may do a one way, one-off replication at startup from the persisted PouchDB into the in-memory PouchDB, then two-way, continuous, retriable sync.
|
||||
|
||||
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
|
||||
|
||||
@@ -129,10 +129,28 @@ To configure you database to use the new schema (i.e. create tables and columns)
|
||||
npx prisma migrate dev
|
||||
```
|
||||
|
||||
To generate a schema in this way with the above example code, you will need to specify your datbase connection string in the environment variable `DATABASE_URL`. You can do this by setting it in a `.env` file at the root of your project.
|
||||
To generate a schema in this way with the above example code, you will need to specify your database connection string in the environment variable `DATABASE_URL`. You can do this by setting it in a `.env` file at the root of your project.
|
||||
|
||||
As this feature is experimental in Prisma, it is behind a feature flag. You should check your database schema manually after using this option. See the [Prisma documentation](https://www.prisma.io/docs/) for information on how to use `prisma migrate`.
|
||||
|
||||
:::tip
|
||||
If you experience issues with Prisma opening too many database connections in local development mode (e.g. due to Hot Module Reloading) you can use an approach like this when initalising the Prisma Client:
|
||||
|
||||
```javascript title="pages/api/auth/[...nextauth].js"
|
||||
let prisma
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
prisma = new PrismaClient()
|
||||
} else {
|
||||
if (!global.prisma) {
|
||||
global.prisma = new PrismaClient()
|
||||
}
|
||||
prisma = global.prisma
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
### Custom Models
|
||||
|
||||
You can add properties to the schema and map them to any database column names you wish, but you should not change the base properties or types defined in the example schema.
|
||||
@@ -154,21 +172,3 @@ adapter: Adapters.Prisma.Adapter({
|
||||
})
|
||||
...
|
||||
```
|
||||
|
||||
:::tip
|
||||
If you experience issues with Prisma opening too many database connections in local development mode (e.g. due to Hot Module Reloading) you can use an approach like this when initalising the Prisma Client:
|
||||
|
||||
```javascript title="pages/api/auth/[...nextauth].js"
|
||||
let prisma
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
prisma = new PrismaClient()
|
||||
} else {
|
||||
if (!global.prisma) {
|
||||
global.prisma = new PrismaClient()
|
||||
}
|
||||
prisma = global.prisma
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
@@ -124,7 +124,7 @@ To configure you database to use the new schema (i.e. create tables and columns)
|
||||
npx prisma migrate dev
|
||||
```
|
||||
|
||||
To generate a schema in this way with the above example code, you will need to specify your datbase connection string in the environment variable `DATABASE_URL`. You can do this by setting it in a `.env` file at the root of your project.
|
||||
To generate a schema in this way with the above example code, you will need to specify your database connection string in the environment variable `DATABASE_URL`. You can do this by setting it in a `.env` file at the root of your project.
|
||||
|
||||
As this feature is experimental in Prisma, it is behind a feature flag. You should check your database schema manually after using this option. See the [Prisma documentation](https://www.prisma.io/docs/) for information on how to use `prisma migrate`.
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ callbacks: {
|
||||
|
||||
* When using the **Email Provider** the `signIn()` callback is triggered both when the user makes a **Verification Request** (before they are sent email with a link that will allow them to sign in) and again *after* they activate the link in the sign in email.
|
||||
|
||||
Email accounts do not have profiles in the same way OAuth accounts do. On the first call during email sign in the `profile` object will include an property `verificationRequest: true` to indicate it is being triggered in the verification request flow. When the callback is invoked *after* a user has clicked on a sign in link, this property will not be present.
|
||||
Email accounts do not have profiles in the same way OAuth accounts do. On the first call during email sign in the `profile` object will include a property `verificationRequest: true` to indicate it is being triggered in the verification request flow. When the callback is invoked *after* a user has clicked on a sign in link, this property will not be present.
|
||||
|
||||
You can check for the `verificationRequest` property to avoid sending emails to addresses or domains on a blocklist (or to only explicitly generate them for email address in an allow list).
|
||||
|
||||
@@ -78,6 +78,11 @@ When using NextAuth.js with a database, the User object will be either a user ob
|
||||
When using NextAuth.js without a database, the user object it will always be a prototype user object, with information extracted from the profile.
|
||||
:::
|
||||
|
||||
:::note
|
||||
Redirects returned by this callback cancel the authentication flow. Only redirect to error pages that, for example, tell the user why they're not allowed to sign in.
|
||||
|
||||
To redirect to a page after a successful sign in, please use [the `callbackUrl` option](/getting-started/client#specifying-a-callbackurl) or [the redirect callback](/configuration/callbacks#redirect-callback).
|
||||
:::
|
||||
|
||||
## Redirect callback
|
||||
|
||||
@@ -158,7 +163,7 @@ If you need to persist a large amount of data, you will need to persist it elsew
|
||||
|
||||
## Session callback
|
||||
|
||||
The session callback is called whenever a session is checked. By default, only a subset of the token is returned for increased security. If you want to make something available you added to the token through the `jwt()` callback, you have to explicitely forward it here to make it available to the client.
|
||||
The session callback is called whenever a session is checked. By default, only a subset of the token is returned for increased security. If you want to make something available you added to the token through the `jwt()` callback, you have to explicitly forward it here to make it available to the client.
|
||||
|
||||
e.g. `getSession()`, `useSession()`, `/api/auth/session`
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ See the [providers documentation](/configuration/providers) for a list of suppor
|
||||
|
||||
#### Description
|
||||
|
||||
A random string used to hash tokens, sign cookies and generate crytographic keys.
|
||||
A random string used to hash tokens, sign cookies and generate cryptographic keys.
|
||||
|
||||
If not specified, it uses a hash for all configuration options, including Client ID / Secrets for entropy.
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ This error occurs when the `useSession()` React Hook has a problem fetching sess
|
||||
|
||||
#### CLIENT_FETCH_ERROR
|
||||
|
||||
If you see `CLIENT_FETCH_ERROR` make sure you have configured the `NEXTAUTH_URL` envionment variable.
|
||||
If you see `CLIENT_FETCH_ERROR` make sure you have configured the `NEXTAUTH_URL` environment variable.
|
||||
|
||||
---
|
||||
|
||||
@@ -63,9 +63,9 @@ The Email authentication provider can only be used if a database is configured.
|
||||
|
||||
The Credentials Provider can only be used if JSON Web Tokens are used for sessions.
|
||||
|
||||
JSON Web Tokens are used for Sessions by default if you have not specified a database. However if you are using a database, then Database Sessions are enabled by default and you need to [explictly enable JWT Sessions](https://next-auth.js.org/configuration/options#session) to use the Credentials Provider.
|
||||
JSON Web Tokens are used for Sessions by default if you have not specified a database. However if you are using a database, then Database Sessions are enabled by default and you need to [explicitly enable JWT Sessions](https://next-auth.js.org/configuration/options#session) to use the Credentials Provider.
|
||||
|
||||
If you are using a Credentials Provider, NextAuth.js will not persist users or sessions in a database - user accounts used with the Credentials Provider must be created and manged outside of NextAuth.js.
|
||||
If you are using a Credentials Provider, NextAuth.js will not persist users or sessions in a database - user accounts used with the Credentials Provider must be created and managed outside of NextAuth.js.
|
||||
|
||||
In _most cases_ it does not make sense to specify a database in NextAuth.js options and support a Credentials Provider.
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ JSON Web Tokens can be used for session tokens, but are also used for lots of ot
|
||||
|
||||
* You can enable encryption (JWE) to store include information directly in a JWT session token that you wish to keep secret and use the token to pass information between services / APIs on the same domain.
|
||||
|
||||
* You can use JWT to securely store information you do not mind the client knowing even without encryption, as the JWT is stored in an server-readable-only-token so data in the JWT is not accessible to third party JavaScript running on your site.
|
||||
* 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-token so data in the JWT is not accessible to third party JavaScript running on your site.
|
||||
|
||||
### What are the disadvantages of JSON Web Tokens?
|
||||
|
||||
|
||||
@@ -22,15 +22,15 @@ The NextAuth.js client library makes it easy to interact with sessions from Reac
|
||||
:::tip
|
||||
The session data returned to the client does not contain sensitive information such as the Session Token or OAuth tokens. It contains a minimal payload that includes enough data needed to display information on a page about the user who is signed in for presentation purposes (e.g name, email, image).
|
||||
|
||||
You can use the [session callback](/configuration/callbacks#session) to customize the session object returned to the client if you need to return additional data in the session object.
|
||||
You can use the [session callback](/configuration/callbacks#session-callback) to customize the session object returned to the client if you need to return additional data in the session object.
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## 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,11 +202,11 @@ 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). 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 to allow other URLs, including supporting relative URLs.
|
||||
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.
|
||||
|
||||
#### Using the redirect: false option
|
||||
|
||||
@@ -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
|
||||
@@ -294,7 +290,7 @@ As with the `signIn()` function, you can specify a `callbackUrl` parameter by pa
|
||||
|
||||
e.g. `signOut({ callbackUrl: 'http://localhost:3000/foo' })`
|
||||
|
||||
The URL must be considered valid by the [redirect callback handler](/configuration/callbacks#redirect). By default this means it must be an absolute URL at the same hostname (or else it will default to the homepage); you can define your own custom redirect callback to allow other URLs, including supporting relative URLs.
|
||||
The URL must be considered valid by the [redirect callback handler](/configuration/callbacks#redirect-callback). By default this means it must be an absolute URL at the same hostname (or else it will default to the homepage); you can define your own custom [redirect callback](/configuration/callbacks#redirect-callback) to allow other URLs, including supporting relative URLs.
|
||||
|
||||
#### Using the redirect: false option
|
||||
|
||||
@@ -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} />
|
||||
@@ -344,7 +340,7 @@ export async function getServerSideProps(ctx) {
|
||||
}
|
||||
```
|
||||
|
||||
If every one of your pages needs to be protected, you can do this in `_app`, otherwise you can do it on a page-by-page basis. Alternatively, there is you can do per page authentication checks client side, instead of having each auth check be blocking (SSR) by using the method described below in [alternative client session handling](#custom-client-session-handling).
|
||||
If every one of your pages needs to be protected, you can do this in `_app`, otherwise you can do it on a page-by-page basis. Alternatively, you can do per page authentication checks client side, instead of having each auth check be blocking (SSR) by using the method described below in [alternative client session handling](#custom-client-session-handling).
|
||||
|
||||
### Options
|
||||
|
||||
@@ -360,7 +356,7 @@ import { Provider } from 'next-auth/client'
|
||||
export default function App ({ Component, pageProps }) {
|
||||
return (
|
||||
<Provider session={pageProps.session}
|
||||
options={{
|
||||
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
|
||||
@@ -385,7 +381,7 @@ The `clientMaxAge` option is the maximum age a session data can be on the client
|
||||
|
||||
When `clientMaxAge` is set to `0` (the default) the cache will always be used when useSession is called and only explicit calls made to get the session status (i.e. `getSession()`) or event triggers, such as signing in or out in another tab/window, or a tab/window gaining or losing focus, will trigger an update of the session state.
|
||||
|
||||
If set to any value other than zero, it specifies in seconds the maxium age of session data on the client before the `useSession()` hook will call the server again to sync the session state.
|
||||
If set to any value other than zero, it specifies in seconds the maximum age of session data on the client before the `useSession()` hook will call the server again to sync the session state.
|
||||
|
||||
Unless you have a short session expiry time (e.g. < 24 hours) you probably don't need to change this option. Setting this option to too short a value will increase load (and potentially hosting costs).
|
||||
|
||||
@@ -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 contant 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.
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ providers: [
|
||||
|
||||
:::tip
|
||||
|
||||
You can convert your Apple key to a single line to use it in a environment variable.
|
||||
You can convert your Apple key to a single line to use it in an environment variable.
|
||||
|
||||
**Mac**
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ You can override any of the options to suit your own use case.
|
||||
- When asked for a redirection URL, use http://localhost:3000/api/auth/callback/azure-ad-b2c
|
||||
- Create a new secret and remember / copy its value immediately, it will disappear.
|
||||
|
||||
In `.env.local` create the follwing entries:
|
||||
In `.env.local` create the following entries:
|
||||
|
||||
```
|
||||
AZURE_CLIENT_ID=<copy Application (client) ID here>
|
||||
|
||||
38
www/docs/providers/coinbase.md
Normal file
38
www/docs/providers/coinbase.md
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
id: coinbase
|
||||
title: Coinbase
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
https://developers.coinbase.com/api/v2
|
||||
|
||||
## Configuration
|
||||
|
||||
https://www.coinbase.com/settings/api
|
||||
|
||||
## Options
|
||||
|
||||
The **Coinbase Provider** comes with a set of default options:
|
||||
|
||||
- [Coinbase Provider options](https://github.com/nextauthjs/next-auth/blob/main/src/providers/coinbase.js)
|
||||
|
||||
You can override any of the options to suit your own use case.
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
import Providers from `next-auth/providers`
|
||||
...
|
||||
providers: [
|
||||
Providers.Coinbase({
|
||||
clientId: process.env.COINBASE_CLIENT_ID,
|
||||
clientSecret: process.env.COINBASE_CLIENT_SECRET
|
||||
})
|
||||
]
|
||||
...
|
||||
```
|
||||
|
||||
:::tip
|
||||
This Provider template has a 2 hour access token to it. A refresh token is also returned.
|
||||
:::
|
||||
@@ -83,7 +83,7 @@ See the [callbacks documentation](/configuration/callbacks) for more information
|
||||
|
||||
You can specify more than one credentials provider by specifying a unique `id` for each one.
|
||||
|
||||
You can also use them in conjuction with other provider options.
|
||||
You can also use them in conjunction with other provider options.
|
||||
|
||||
As with all providers, the order you specify them is the order they are displayed on the sign in page.
|
||||
|
||||
|
||||
@@ -46,5 +46,5 @@ Email address is not returned by the Instagram API.
|
||||
:::
|
||||
|
||||
:::tip
|
||||
Instagram display app required callback URL to be configured in your Facebook app and Facebook required you to use **https** even for localhost! In order to do that, you either need to [add an SSL to your localhost](https://www.freecodecamp.org/news/how-to-get-https-working-on-your-local-development-environment-in-5-minutes-7af615770eec/) or use a proxy such as [ngrock](https://ngrok.com/docs).
|
||||
Instagram display app required callback URL to be configured in your Facebook app and Facebook required you to use **https** even for localhost! In order to do that, you either need to [add an SSL to your localhost](https://www.freecodecamp.org/news/how-to-get-https-working-on-your-local-development-environment-in-5-minutes-7af615770eec/) or use a proxy such as [ngrok](https://ngrok.com/docs).
|
||||
:::
|
||||
|
||||
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
|
||||
})
|
||||
]
|
||||
...
|
||||
```
|
||||
@@ -83,6 +83,10 @@ This example shows how to implement a fullstack app in TypeScript with Next.js u
|
||||
|
||||
This `dev.to` tutorial walks one through adding NextAuth.js to an existing project. Including setting up the OAuth client id and secret, adding the API routes for authentication, protecting pages and API routes behind that authentication, etc.
|
||||
|
||||
### [Introduction to NextAuth.js](https://www.youtube.com/watch?v=npZsJxWntJM)
|
||||
|
||||
This is an introductory video to NextAuth.js for beginners. In this video, it is explained how to set up authentication in a few easy steps and add different configurations to make it more robust and secure.
|
||||
|
||||
### [Adding Sign in With Apple Next JS](https://thesiddd.com/blog/apple-auth)
|
||||
|
||||
This tutorial walks step by step on how to get Sign In with Apple working (both locally and on a deployed website) using NextAuth.js.
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -49,6 +49,7 @@ module.exports = {
|
||||
"adapters/prisma-legacy",
|
||||
"adapters/dynamodb",
|
||||
"adapters/firebase",
|
||||
"adapters/pouchdb",
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user