Compare commits

...

18 Commits

Author SHA1 Message Date
Sangwon Park
5a89ab69d3 feat(provider): add Naver provider (#2172)
* add Naver provider

* fix typo

* Update src/providers/naver.js

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

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2021-06-16 00:46:41 +02:00
Balázs Orbán
665445818e docs(config): link to next documentation instead of canary 2021-06-12 17:11:53 +02:00
ndom91
67cf2a11bb docs: fix alt client provider example 2021-06-12 16:42:48 +02:00
Lluis Agusti
832d51f10e test(client): add more tests (#2135)
Contains the following squashed commits:

* test(client): verify CSRF Token fetch
* test(client): verify `getProviders` logic
* test(client): verify `useSession` happy path
* test(coverage): initial coverage setup (trial)
* chore(test): fix coverage reporting
* chore(test): define report directory for codecov

Co-authored-by: Balázs Orbán <info@balazsorban.com>
2021-06-10 11:42:58 +02:00
Balázs Orbán
29862ac887 fix(build): do not run husky on postinstall (#2158) 2021-06-10 00:24:06 +02:00
Christopher Betz
5aa2b61b88 feat(provider): add Coinbase provider (#2153)
Co-authored-by: Balázs Orbán <info@balazsorban.com>
2021-06-09 22:46:12 +02:00
Nicholas Chiang
929c644653 docs(client): fix callback anchor links (#2151) 2021-06-09 22:19:26 +02:00
Nicholas Chiang
2657e72e81 docs(callbacks): don't use signIn for redirects (#2150)
Specifies that you shouldn't use the `signIn` callback for arbitrary redirects. Instead, use the `callbackUrl` option or the redirect callback.
2021-06-09 22:17:45 +02:00
Apoorv Taneja
8ff7dbb18f docs(tutorial): Adding a YouTube link for NextAuth.js introduction (#2047)
Co-authored-by: Lluis Agusti <hi@llu.lu>
2021-06-09 17:04:41 +02:00
Manish Kumar
748d576a5a docs(adapter): align DynamoDB docs with source code (#2125)
* Updated DynamoDB Adaptor documentation

* Update dynamodb.md

* Update dynamodb.md

* Update dynamodb.md
2021-06-09 17:01:00 +02:00
Camille Gabrieli
9f16e3f0fb docs(client): fix typo (#2139) 2021-06-08 09:02:41 +02:00
Adrian Artiles
1042e9a93d docs: fix typos (#2136) 2021-06-08 08:57:13 +02:00
Nico Domino
aa57f2dd7e docs(prisma-legacy): update tip location
Move client tip up to client section of docs
2021-06-07 22:44:04 +02:00
Nico Domino
1817286ce3 Update pouchdb.md 2021-06-07 22:21:39 +02:00
Nico Domino
b942dd34f3 docs(pouchdb): add pouchdb page (#2140) 2021-06-07 17:10:42 +02:00
Lluis Agusti
4d9622e1cc chore(git): fix git hooks (#2130)
Contains the following squashed commits:

* chore(git): fix husky pre-commit
* chore(husky): install git hooks on `postinstall`
2021-06-04 12:55:41 +02:00
sanctuxm
a7eadf80e5 docs(provider): fix ngrok typo on instagram provider docs (#2121) 2021-06-03 10:35:07 +02:00
Manish Kumar
75c7dbc3e7 docs(adapter): fix file location in DynamoDB docs (#2120) 2021-06-03 10:11:45 +02:00
35 changed files with 583 additions and 127 deletions

View File

@@ -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
View File

@@ -58,4 +58,7 @@ app/yarn.lock
/_work
# Prisma migrations
/prisma/migrations
/prisma/migrations
# Tests
/coverage

View File

@@ -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"

View File

@@ -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.

View File

@@ -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

View File

@@ -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",
}

View 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>
)
}

View 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>
</>
)
}

View File

@@ -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"),
}

View 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>
</>
)
}

View File

@@ -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>
}

View File

@@ -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>
</>
)
}

View File

@@ -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
View 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
View 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,
}
}

View File

@@ -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"

View File

@@ -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 :

View 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

View File

@@ -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
}
```
:::

View File

@@ -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`.

View File

@@ -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`

View File

@@ -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.

View File

@@ -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.

View File

@@ -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?

View File

@@ -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.

View File

@@ -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**

View File

@@ -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>

View 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.
:::

View File

@@ -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.

View File

@@ -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).
:::

View 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
})
]
...
```

View File

@@ -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.

View File

@@ -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",
},
],
},

View File

@@ -49,6 +49,7 @@ module.exports = {
"adapters/prisma-legacy",
"adapters/dynamodb",
"adapters/firebase",
"adapters/pouchdb",
],
},
{