mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
13 Commits
next-auth@
...
feat/oauth
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f48e0f1e2d | ||
|
|
2eef2f85d5 | ||
|
|
bf31512071 | ||
|
|
acc9966285 | ||
|
|
6dd44000d7 | ||
|
|
2267292f5f | ||
|
|
883b36e2b2 | ||
|
|
029edb93ee | ||
|
|
ebcc3280df | ||
|
|
70a16e82c3 | ||
|
|
1365211a4e | ||
|
|
6df5773220 | ||
|
|
e3e3cf12d1 |
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@@ -69,8 +69,7 @@ jobs:
|
||||
git config --global user.name "Balázs Orbán"
|
||||
pnpm release
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN_PKG: ${{ secrets.NPM_TOKEN_PKG }}
|
||||
NPM_TOKEN_ORG: ${{ secrets.NPM_TOKEN_ORG }}
|
||||
release-pr:
|
||||
|
||||
@@ -16,21 +16,21 @@
|
||||
},
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@next-auth/fauna-adapter": "^1",
|
||||
"@next-auth/prisma-adapter": "^1",
|
||||
"@prisma/client": "^3",
|
||||
"faunadb": "^4",
|
||||
"next": "12.1.7-canary.51",
|
||||
"nodemailer": "^6",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"concurrently": "^7",
|
||||
"@next-auth/fauna-adapter": "^1.0.1",
|
||||
"@next-auth/prisma-adapter": "^1.0.1",
|
||||
"@prisma/client": "^3.10.0",
|
||||
"cpx": "^1.5.0",
|
||||
"fake-smtp-server": "^0.8.0",
|
||||
"prisma": "^3"
|
||||
"faunadb": "^4.4.1",
|
||||
"next": "^12.1.0",
|
||||
"nodemailer": "^6.7.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^17.0.37",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"concurrently": "^7.1.0",
|
||||
"prisma": "^3.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,10 +46,7 @@ import BoxyHQSAMLProvider from "next-auth/providers/boxyhq-saml"
|
||||
// })
|
||||
// const adapter = FaunaAdapter(client)
|
||||
export const authOptions: NextAuthOptions = {
|
||||
// adapter: {
|
||||
// getUserByEmail: (email) => ({ id: "1", email, emailVerified: null }),
|
||||
// createVerificationToken: (token) => token,
|
||||
// } as any,
|
||||
// adapter,
|
||||
providers: [
|
||||
// E-mail
|
||||
// Start fake e-mail server with `npm run start:email`
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// This is an example of to protect an API route
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { getSession } from "next-auth/react"
|
||||
|
||||
export default async (req, res) => {
|
||||
const session = await unstable_getServerSession(req, res, options)
|
||||
const session = await getSession({ req })
|
||||
|
||||
if (session) {
|
||||
res.send({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// This is an example of how to access a session from an API route
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { getSession } from "next-auth/react"
|
||||
|
||||
export default async (req, res) => {
|
||||
const session = await unstable_getServerSession(req, res, authOptions)
|
||||
const session = await getSession({ req })
|
||||
res.send(JSON.stringify(session, null, 2))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
export { default } from "next-auth/middleware"
|
||||
|
||||
export const config = { matcher: ["/middleware-protected"] }
|
||||
|
||||
// Other ways to use this middleware
|
||||
|
||||
// import withAuth from "next-auth/middleware"
|
||||
@@ -1,5 +1,5 @@
|
||||
// This is an example of how to protect content using server rendering
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "./api/auth/[...nextauth]"
|
||||
import Layout from "../components/layout"
|
||||
import AccessDenied from "../components/access-denied"
|
||||
@@ -26,11 +26,7 @@ export default function Page({ content, session }) {
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const session = await unstable_getServerSession(
|
||||
context.req,
|
||||
context.res,
|
||||
authOptions
|
||||
)
|
||||
const session = await getServerSession(context, authOptions)
|
||||
let content = null
|
||||
|
||||
if (session) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { getSession } from "next-auth/react"
|
||||
import Layout from "../components/layout"
|
||||
|
||||
export default function Page() {
|
||||
@@ -11,17 +11,13 @@ export default function Page() {
|
||||
<Layout>
|
||||
<h1>Server Side Rendering</h1>
|
||||
<p>
|
||||
This page uses the <strong>unstable_getServerSession()</strong> method
|
||||
in <strong>getServerSideProps()</strong>.
|
||||
This page uses the universal <strong>getSession()</strong> method in{" "}
|
||||
<strong>getServerSideProps()</strong>.
|
||||
</p>
|
||||
<p>
|
||||
Using <strong>unstable_getServerSession()</strong> in{" "}
|
||||
<strong>getServerSideProps()</strong> is currently the recommended
|
||||
approach, although the API may still change, if you need to support
|
||||
Server Side Rendering with authentication.
|
||||
</p>
|
||||
<p>
|
||||
Using <strong>getSession()</strong> is still recommended on the client.
|
||||
Using <strong>getSession()</strong> in{" "}
|
||||
<strong>getServerSideProps()</strong> is the recommended approach if you
|
||||
need to support Server Side Rendering with authentication.
|
||||
</p>
|
||||
<p>
|
||||
The advantage of Server Side Rendering is this page does not require
|
||||
@@ -39,11 +35,7 @@ export default function Page() {
|
||||
export async function getServerSideProps(context) {
|
||||
return {
|
||||
props: {
|
||||
session: await unstable_getServerSession(
|
||||
contex.req,
|
||||
contex.res,
|
||||
authOptions
|
||||
),
|
||||
session: await getSession(context),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ You **can** skip configuring a database and come back to it later if you want.
|
||||
For more information about setting up a database, please check out the following links:
|
||||
|
||||
* Docs: [next-auth.js.org/adapters/overview](https://next-auth.js.org/adapters/overview)
|
||||
* Adapters Repo: [nextauthjs/adapters](https://github.com/nextauthjs/adapters)
|
||||
|
||||
### 3. Configure Authentication Providers
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
"dependencies": {
|
||||
"dotenv": "^16.0.0",
|
||||
"gatsby": "next",
|
||||
"next-auth": "latest",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
"next-auth": "^4.2.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vercel": "^23.1.2"
|
||||
|
||||
@@ -68,6 +68,7 @@ You **can** skip configuring a database and come back to it later if you want.
|
||||
For more information about setting up a database, please check out the following links:
|
||||
|
||||
* Docs: [next-auth.js.org/adapters/overview](https://next-auth.js.org/adapters/overview)
|
||||
* Adapters Repo: [nextauthjs/adapters](https://github.com/nextauthjs/adapters)
|
||||
|
||||
### 3. Configure Authentication Providers
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import { withAuth } from "next-auth/middleware"
|
||||
|
||||
// More on how NextAuth.js middleware works: https://next-auth.js.org/configuration/nextjs#middleware
|
||||
export default withAuth({
|
||||
callbacks: {
|
||||
authorized: ({ req, token }) =>
|
||||
// /admin requires admin role, but /me only requires the user to be logged in.
|
||||
req.nextUrl.pathname !== "/admin" || token?.userRole === "admin",
|
||||
},
|
||||
})
|
||||
|
||||
export const config = { matcher: ["/admin", "/me"] }
|
||||
@@ -23,16 +23,16 @@
|
||||
],
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"next": "12.1.7-canary.51",
|
||||
"next": "^12.0.11-canary.4",
|
||||
"next-auth": "latest",
|
||||
"nodemailer": "^6",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
"nodemailer": "^6.6.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^17",
|
||||
"@types/react": "^18",
|
||||
"typescript": "^4"
|
||||
"@types/node": "^17.0.14",
|
||||
"@types/react": "^17.0.39",
|
||||
"typescript": "^4.5.5"
|
||||
},
|
||||
"prettier": {
|
||||
"semi": false
|
||||
|
||||
8
apps/example-nextjs/pages/admin/_middleware.ts
Normal file
8
apps/example-nextjs/pages/admin/_middleware.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { withAuth } from "next-auth/middleware"
|
||||
|
||||
// More on how NextAuth.js middleware works: https://next-auth.js.org/configuration/nextjs#middleware
|
||||
export default withAuth({
|
||||
callbacks: {
|
||||
authorized: ({ token }) => token?.userRole === "admin",
|
||||
},
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
import Layout from "../components/layout"
|
||||
import Layout from "../../components/layout"
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
@@ -1,4 +1,4 @@
|
||||
import NextAuth, { NextAuthOptions } from "next-auth"
|
||||
import NextAuth from "next-auth"
|
||||
import GoogleProvider from "next-auth/providers/google"
|
||||
import FacebookProvider from "next-auth/providers/facebook"
|
||||
import GithubProvider from "next-auth/providers/github"
|
||||
@@ -9,7 +9,7 @@ import Auth0Provider from "next-auth/providers/auth0"
|
||||
|
||||
// For more information on each option (and a full list of options) go to
|
||||
// https://next-auth.js.org/configuration/options
|
||||
export const authOptions: NextAuthOptions = {
|
||||
export default NextAuth({
|
||||
// https://next-auth.js.org/configuration/providers/oauth
|
||||
providers: [
|
||||
/* EmailProvider({
|
||||
@@ -18,7 +18,7 @@ export const authOptions: NextAuthOptions = {
|
||||
}),
|
||||
// Temporarily removing the Apple provider from the demo site as the
|
||||
// callback URL for it needs updating due to Vercel changing domains
|
||||
|
||||
|
||||
Providers.Apple({
|
||||
clientId: process.env.APPLE_ID,
|
||||
clientSecret: {
|
||||
@@ -60,6 +60,4 @@ export const authOptions: NextAuthOptions = {
|
||||
return token
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default NextAuth(authOptions)
|
||||
})
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// This is an example of to protect an API route
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { getSession } from "next-auth/react"
|
||||
import type { NextApiRequest, NextApiResponse } from "next"
|
||||
|
||||
export default async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session = await unstable_getServerSession(req, res, authOptions)
|
||||
const session = await getSession({ req })
|
||||
|
||||
if (session) {
|
||||
res.send({
|
||||
@@ -12,8 +12,7 @@ export default async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
})
|
||||
} else {
|
||||
res.send({
|
||||
error:
|
||||
"You must be signed in to view the protected content on this page.",
|
||||
error: "You must be signed in to view the protected content on this page.",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
2
apps/example-nextjs/pages/me/_middleware.ts
Normal file
2
apps/example-nextjs/pages/me/_middleware.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
// More on how NextAuth.js middleware works: https://next-auth.js.org/configuration/nextjs#middleware
|
||||
export { default } from "next-auth/middleware"
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useSession } from "next-auth/react"
|
||||
import Layout from "../components/layout"
|
||||
import Layout from "../../components/layout"
|
||||
|
||||
export default function MePage() {
|
||||
const { data } = useSession()
|
||||
@@ -1,24 +1,26 @@
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "./api/auth/[...nextauth]"
|
||||
import { useSession, getSession } from "next-auth/react"
|
||||
import Layout from "../components/layout"
|
||||
import type { NextPageContext } from "next"
|
||||
|
||||
export default function ServerSidePage({ session }) {
|
||||
export default function ServerSidePage() {
|
||||
// As this page uses Server Side Rendering, the `session` will be already
|
||||
// populated on render without needing to go through a loading stage.
|
||||
// This is possible because of the shared context configured in `_app.js` that
|
||||
// is used by `useSession()`.
|
||||
const { data: session, status } = useSession()
|
||||
const loading = status === "loading"
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<h1>Server Side Rendering</h1>
|
||||
<p>
|
||||
This page uses the <strong>unstable_getServerSession()</strong> method
|
||||
in <strong>unstable_getServerSideProps()</strong>.
|
||||
This page uses the universal <strong>getSession()</strong> method in{" "}
|
||||
<strong>getServerSideProps()</strong>.
|
||||
</p>
|
||||
<p>
|
||||
Using <strong>unstable_getServerSession()</strong> in{" "}
|
||||
<strong>unstable_getServerSideProps()</strong> is the recommended
|
||||
approach if you need to support Server Side Rendering with
|
||||
authentication.
|
||||
Using <strong>getSession()</strong> in{" "}
|
||||
<strong>getServerSideProps()</strong> is the recommended approach if you
|
||||
need to support Server Side Rendering with authentication.
|
||||
</p>
|
||||
<p>
|
||||
The advantage of Server Side Rendering is this page does not require
|
||||
@@ -36,7 +38,7 @@ export default function ServerSidePage({ session }) {
|
||||
export async function getServerSideProps(context: NextPageContext) {
|
||||
return {
|
||||
props: {
|
||||
session: await unstable_getServerSession(context.req, context.res, authOptions),
|
||||
session: await getSession(context),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"cookie": "0.4.1",
|
||||
"next-auth": "workspace:*"
|
||||
"next-auth": "^4.3.3"
|
||||
},
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
|
||||
@@ -1232,10 +1232,10 @@ natural-compare@^1.4.0:
|
||||
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
|
||||
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
|
||||
|
||||
next-auth@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/next-auth/-/next-auth-4.5.0.tgz#2df57287fddc705b8971c88c60bad44a89ac6dd1"
|
||||
integrity sha512-B6gYRIbqtj8nlDsx3y2Ruwp/mvZnItPs7VUULY43QYw+M9xtDPIM9EBZ3ryd/wNYA3MDteBJlzGm/ivseXcmJA==
|
||||
next-auth@^4.3.3:
|
||||
version "4.3.3"
|
||||
resolved "https://registry.yarnpkg.com/next-auth/-/next-auth-4.3.3.tgz#5ff892e73648a0f33c2af0e9d7cafda729f63ae7"
|
||||
integrity sha512-bUs+oOOPT18Pq/+4v9q4PA/DGoVoAX6jwY7RTfE/akFXwlny+y/mNS6lPSUwpqcHjljqBaq34PQA3+01SdOOPw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.16.3"
|
||||
"@panva/hkdf" "^1.0.1"
|
||||
|
||||
@@ -11,7 +11,7 @@ This is the Dgraph Adapter for [`next-auth`](https://next-auth.js.org).
|
||||
|
||||
1. Install the necessary packages
|
||||
|
||||
```bash npm2yarn2pnpm
|
||||
```bash npm2yarn
|
||||
npm install next-auth @next-auth/dgraph-adapter
|
||||
```
|
||||
|
||||
@@ -226,22 +226,22 @@ database you must customize next-auth `encode` and `decode` functions, as the de
|
||||
further customize the jwt with roles if you want to implement [`RBAC logic`](https://dgraph.io/docs/graphql/authorization/directive/#role-based-access-control).
|
||||
|
||||
```js
|
||||
import * as jwt from "jsonwebtoken"
|
||||
import * as jwt from "jsonwebtoken";
|
||||
export default NextAuth({
|
||||
session: {
|
||||
strategy: "jwt",
|
||||
strategy: "jwt"
|
||||
},
|
||||
jwt: {
|
||||
secret: process.env.SECRET,
|
||||
encode: async ({ secret, token }) => {
|
||||
return jwt.sign({ ...token, userId: token.id }, secret, {
|
||||
return jwt.sign({...token, userId: token.id}, secret, {
|
||||
algorithm: "HS256",
|
||||
expiresIn: 30 * 24 * 60 * 60, // 30 days
|
||||
})
|
||||
expiresIn: 30 * 24 * 60 * 60; // 30 days
|
||||
});
|
||||
},
|
||||
decode: async ({ secret, token }) => {
|
||||
return jwt.verify(token, secret, { algorithms: ["HS256"] })
|
||||
},
|
||||
return jwt.verify(token, secret, { algorithms: ["HS256"] });
|
||||
}
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
@@ -15,7 +15,7 @@ You can find the full schema in the table structure section below.
|
||||
|
||||
1. Install `next-auth` and `@next-auth/dynamodb-adapter`
|
||||
|
||||
```bash npm2yarn2pnpm
|
||||
```bash npm2yarn
|
||||
npm install next-auth @next-auth/dynamodb-adapter
|
||||
```
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ You can find the Fauna schema and seed information in the docs at [next-auth.js.
|
||||
|
||||
1. Install the necessary packages
|
||||
|
||||
```bash npm2yarn2pnpm
|
||||
```bash npm2yarn
|
||||
npm install next-auth @next-auth/fauna-adapter faunadb
|
||||
```
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ This is the Firebase Adapter for [`next-auth`](https://next-auth.js.org). This p
|
||||
|
||||
1. Install the necessary packages
|
||||
|
||||
```bash npm2yarn2pnpm
|
||||
```bash npm2yarn
|
||||
npm install next-auth @next-auth/firebase-adapter@experimental
|
||||
```
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ title: MikroORM
|
||||
|
||||
To use this Adapter, you need to install Mikro ORM, the driver that suits your database, and the separate `@next-auth/mikro-orm-adapter` package:
|
||||
|
||||
```bash npm2yarn2pnpm
|
||||
```bash npm2yarn
|
||||
npm install next-auth @next-auth/mikro-orm-adapter @mikro-orm/core @mikro-orm/[YOUR DRIVER]
|
||||
```
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ The MongoDB adapter does not handle connections automatically, so you will have
|
||||
|
||||
1. Install the necessary packages
|
||||
|
||||
```bash npm2yarn2pnpm
|
||||
```bash npm2yarn
|
||||
npm install next-auth @next-auth/mongodb-adapter mongodb
|
||||
```
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ This is the Neo4j Adapter for [`next-auth`](https://next-auth.js.org). This pack
|
||||
|
||||
1. Install the necessary packages
|
||||
|
||||
```bash npm2yarn2pnpm
|
||||
```bash npm2yarn
|
||||
npm install next-auth @next-auth/neo4j-adapter neo4j-driver
|
||||
```
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ Depending on your architecture you can use PouchDB's http adapter to reach any d
|
||||
|
||||
1. Install `next-auth` and `@next-auth/pouchdb-adapter`
|
||||
|
||||
```bash npm2yarn2pnpm
|
||||
```bash npm2yarn
|
||||
npm install next-auth @next-auth/pouchdb-adapter
|
||||
```
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ title: Prisma
|
||||
|
||||
To use this Adapter, you need to install Prisma Client, Prisma CLI, and the separate `@next-auth/prisma-adapter` package:
|
||||
|
||||
```bash npm2yarn2pnpm
|
||||
```bash npm2yarn
|
||||
npm install next-auth @prisma/client @next-auth/prisma-adapter
|
||||
npm install prisma --save-dev
|
||||
```
|
||||
|
||||
@@ -11,7 +11,7 @@ This is the Sequelize Adapter for [`next-auth`](https://next-auth.js.org).
|
||||
|
||||
1. Install the necessary packages
|
||||
|
||||
```bash npm2yarn2pnpm
|
||||
```bash npm2yarn
|
||||
npm install next-auth @next-auth/sequelize-adapter sequelize
|
||||
```
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ In the future, we might split up this adapter to support single flavors of SQL f
|
||||
|
||||
To use this Adapter, you need to install the following packages:
|
||||
|
||||
```bash npm2yarn2pnpm
|
||||
```bash npm2yarn
|
||||
npm install next-auth @next-auth/typeorm-legacy-adapter typeorm
|
||||
```
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ title: Upstash Redis
|
||||
|
||||
To use this Adapter, you need to install `@upstash/redis` and `@next-auth/upstash-redis-adapter` package:
|
||||
|
||||
```bash npm2yarn2pnpm
|
||||
```bash npm2yarn
|
||||
npm install @upstash/redis @next-auth/upstash-redis-adapter
|
||||
```
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ The redirect callback may be invoked more than once in the same flow.
|
||||
This callback is called whenever a JSON Web Token is created (i.e. at sign
|
||||
in) or updated (i.e whenever a session is accessed in the client). The returned value will be [encrypted](/configuration/options#jwt), and it is stored in a cookie.
|
||||
|
||||
Requests to `/api/auth/signin`, `/api/auth/session` and calls to `getSession()`, `unstable_getServerSession()`, `useSession()` will invoke this function, but only if you are using a [JWT session](/configuration/options#session). This method is not invoked when you persist sessions in a database.
|
||||
Requests to `/api/auth/signin`, `/api/auth/session` and calls to `getSession()`, `useSession()` will invoke this function, but only if you are using a [JWT session](/configuration/options#session). This method is not invoked when you persist sessions in a database.
|
||||
|
||||
- As with database persisted session expiry times, token expiry time is extended whenever a session is active.
|
||||
- The arguments _user_, _account_, _profile_ and _isNewUser_ are only passed the first time this callback is called on a new session, after the user signs in. In subsequent calls, only `token` will be available.
|
||||
|
||||
@@ -53,7 +53,6 @@ The message object will contain:
|
||||
|
||||
- `user`: The user object from your adapter.
|
||||
- `account`: The object returned from the provider.
|
||||
- `profile`: The object returned from the `profile` callback of the OAuth provider.
|
||||
|
||||
### session
|
||||
|
||||
|
||||
@@ -1,73 +1,5 @@
|
||||
# Next.js
|
||||
|
||||
## `unstable_getServerSession`
|
||||
|
||||
:::warning
|
||||
This feature is experimental and may be removed or changed in the future.
|
||||
:::
|
||||
|
||||
When calling from server-side i.e. in API routes or in `getServerSideProps`, we recommend using this function instead of `getSession` to retrieve the `session` object. This method is especially useful when you are using NextAuth.js with a database. This method can _drastically_ reduce response time when used over `getSession` server-side, due to avoiding an extra `fetch` to an API Route (this is generally [not recommended in Next.js](https://nextjs.org/docs/basic-features/data-fetching/get-server-side-props#getserversideprops-or-api-routes)). In addition, `unstable_getServerSession` will correctly update the cookie expiry time and update the session content if `callbacks.jwt` or `callbacks.session` changed something.
|
||||
|
||||
Otherwise, if you only want to get the session token, see [`getToken`](tutorials/securing-pages-and-api-routes#using-gettoken).
|
||||
|
||||
`unstable_getServerSession` requires passing the same object you would pass to `NextAuth` when initializing NextAuth.js. To do so, you can export your NextAuth.js options in the following way:
|
||||
|
||||
In `[...nextauth.js]`:
|
||||
```ts
|
||||
import { NextAuth } from 'next-auth'
|
||||
import type { NextAuthOptions } from 'next-auth'
|
||||
|
||||
export const authOptions: NextAuthOptions = {
|
||||
// your configs
|
||||
}
|
||||
|
||||
export default NextAuth(authOptions);
|
||||
```
|
||||
|
||||
In `getServerSideProps`:
|
||||
```js
|
||||
import { authOptions } from 'pages/api/[...nextauth]'
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const session = await unstable_getServerSession(context.req, context.res, authOptions)
|
||||
|
||||
if (!session) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/',
|
||||
permanent: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
session,
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
In API routes:
|
||||
```js
|
||||
import { authOptions } from 'pages/api/[...nextauth]'
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
|
||||
|
||||
export async function handler(req, res) {
|
||||
const session = await unstable_getServerSession(req, res, authOptions)
|
||||
|
||||
if (!session) {
|
||||
res.status(401).json({ message: "You must be logged in." });
|
||||
return;
|
||||
}
|
||||
|
||||
return res.json({
|
||||
message: 'Success',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Middleware
|
||||
|
||||
You can use a Next.js Middleware with NextAuth.js to protect your site.
|
||||
@@ -86,27 +18,50 @@ You must set the [`NEXTAUTH_SECRET`](/configuration/options#nextauth_secret) env
|
||||
|
||||
|
||||
### Basic usage
|
||||
|
||||
The most simple usage is when you want to require authentication for your entire site. You can add a `middleware.js` file with the following:
|
||||
|
||||
```js
|
||||
export { default } from "next-auth/middleware"
|
||||
import withAuth from "next-auth/middleware"
|
||||
// or
|
||||
import { withAuth } from "next-auth/middleware"
|
||||
```
|
||||
|
||||
That's it! Your application is now secured. 🎉
|
||||
### Custom JWT decode method
|
||||
|
||||
If you only want to secure certain pages, export a `config` object with a `matcher`:
|
||||
If you have custom jwt decode method set in `[...nextauth].ts`, you must also pass the same `decode` method to `withAuth` in order to read the custom-signed JWT correctly. You may want to extract the encode/decode logic to a separate function for consistency.
|
||||
|
||||
```js
|
||||
export { default } from "next-auth/middleware"
|
||||
`[...nextauth].ts`
|
||||
```ts
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
export const config = { matcher: ["/dashboard"] }
|
||||
export default NextAuth({
|
||||
providers: [...],
|
||||
secret: /* Please use `process.env.NEXTAUTH_SECRET` */,
|
||||
jwt: {
|
||||
encode: async ({ secret, token }) => {
|
||||
return jwt.sign(token as any, secret);
|
||||
},
|
||||
decode: async ({ secret, token }) => {
|
||||
return jwt.verify(token as string, secret) as any;
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Now you will still be able to visit every page, but only `/dashboard` will require authentication.
|
||||
|
||||
If a user is not logged in, the default behavior is to redirect them to the sign-in page.
|
||||
Any `_middleware.ts`
|
||||
```ts
|
||||
import withAuth from "next-auth/middleware"
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
export default withAuth({
|
||||
jwt: {
|
||||
decode: async ({ secret, token }) => {
|
||||
return jwt.verify(token, secret) as any;
|
||||
},
|
||||
},
|
||||
callbacks: {
|
||||
authorized: ({ token }) => !!token,
|
||||
},
|
||||
})
|
||||
```
|
||||
---
|
||||
### `callbacks`
|
||||
|
||||
@@ -149,24 +104,46 @@ See the documentation for the [pages option](/configuration/pages) for more info
|
||||
|
||||
---
|
||||
|
||||
### Advanced usage
|
||||
### Examples
|
||||
|
||||
NextAuth.js Middleware is very flexible, there are multiple ways to use it.
|
||||
`withAuth` is very flexible, there are multiple ways to use it.
|
||||
|
||||
:::note
|
||||
If you do not define the options, NextAuth.js will use the default values for the omitted options.
|
||||
:::
|
||||
|
||||
#### default re-export
|
||||
|
||||
```js title="pages/_middleware.js"
|
||||
export { default } from "next-auth/middleware"
|
||||
```
|
||||
|
||||
With this one line, when someone tries to load any of your pages, they will have to be logged-in first. Otherwise, they are redirected to the login page. It will assume that you are using the `NEXTAUTH_SECRET` environment variable.
|
||||
|
||||
#### default `withAuth` export
|
||||
|
||||
```js title="pages/admin/_middleware.js"
|
||||
import { withAuth } from "next-auth/middleware"
|
||||
|
||||
export default withAuth({
|
||||
callbacks: {
|
||||
authorized: ({ token }) => token?.role === "admin",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
With the above code, you just made sure that only user's with the `admin` role can access any of the pages under the `/admin` route. (Including nested routes as well, like `/admin/settings` etc.).
|
||||
|
||||
#### wrap middleware
|
||||
|
||||
```ts title="middleware.ts"
|
||||
```ts title="pages/admin/_middleware.ts"
|
||||
import type { NextRequest } from "next/server"
|
||||
import type { JWT } from "next-auth/jwt"
|
||||
|
||||
import { withAuth } from "next-auth/middleware"
|
||||
|
||||
export default withAuth(
|
||||
// `withAuth` can augment your Request with the user's token.
|
||||
function middleware(req: NextRequest & { nextauth: { token: JWT | null } }) {
|
||||
function middleware(req: NextRequest & { nextauth: { token: JWT } }) {
|
||||
console.log(req.nextauth.token)
|
||||
},
|
||||
{
|
||||
@@ -175,53 +152,12 @@ export default withAuth(
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export const config = { matcher: ["/admin"] }
|
||||
```
|
||||
|
||||
The `middleware` function will only be invoked if the `authorized` callback returns `true`.
|
||||
|
||||
---
|
||||
|
||||
#### Custom JWT decode method
|
||||
|
||||
If you have a custom jwt decode method set in `[...nextauth].ts`, you must also pass the same `decode` method to `withAuth` in order to read the custom-signed JWT correctly. You may want to extract the encode/decode logic to a separate function for consistency.
|
||||
|
||||
``
|
||||
```ts title="/api/auth/[...nextauth].ts"
|
||||
import type { NextAuthOptions } from "next-auth"
|
||||
import NextAuth from "next-auth"
|
||||
import jwt from "jsonwebtoken"
|
||||
|
||||
export const authOptions: NextAuthOptions = {
|
||||
providers: [...],
|
||||
jwt: {
|
||||
async encode({ secret, token }) {
|
||||
return jwt.sign(token, secret)
|
||||
},
|
||||
async decode({ secret, token }) {
|
||||
return jwt.verify(token, secret)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default NextAuth(authOptions)
|
||||
```
|
||||
|
||||
And:
|
||||
|
||||
```ts title="middleware.ts"
|
||||
import withAuth from "next-auth/middleware"
|
||||
import { authOptions } from "pages/api/auth/[...nextauth]";
|
||||
|
||||
export default withAuth({
|
||||
jwt: { decode: authOptions.jwt },
|
||||
callbacks: {
|
||||
authorized: ({ token }) => !!token,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Caveats
|
||||
|
||||
- Currently only supports session verification, as parts of the sign-in code need to run in a Node.js environment. In the future, we would like to make sure that NextAuth.js can fully run at the [Edge](https://nextjs.org/docs/api-reference/edge-runtime)
|
||||
|
||||
@@ -366,14 +366,11 @@ Changes the color scheme theme of [pages](/configuration/pages) as well as allow
|
||||
|
||||
In addition, you can define a logo URL in `theme.logo` which will be rendered above the main card in the default signin/signout/error/verify-request pages, as well as a `theme.brandColor` which will affect the accent color of these pages.
|
||||
|
||||
The sign-in button's background color will match the `brandColor` and defaults to `"#346df1"`. The text color is `#fff` by default, but if your brand color gives a weak contrast, correct it with the `buttonText` color option.
|
||||
|
||||
```js
|
||||
theme: {
|
||||
colorScheme: "auto", // "auto" | "dark" | "light"
|
||||
brandColor: "", // Hex color code
|
||||
logo: "", // Absolute URL to image
|
||||
buttonText: "" // Hex color code
|
||||
logo: "" // Absolute URL to image
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -126,10 +126,10 @@ function Auth({ children }) {
|
||||
// if `{ required: true }` is supplied, `status` can only be "loading" or "authenticated"
|
||||
const { status } = useSession({ required: true })
|
||||
|
||||
if (status === "loading") {
|
||||
if (status === 'loading') {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
|
||||
return children
|
||||
}
|
||||
```
|
||||
@@ -161,19 +161,13 @@ See repository [`README`](https://github.com/nextauthjs/react-query) for more de
|
||||
## getSession()
|
||||
|
||||
- Client Side: **Yes**
|
||||
- Server Side: **No** (See: [`unstable_getServerSession()`](/configuration/nextjs#unstable_getserversession)
|
||||
- Server Side: **Yes**
|
||||
|
||||
NextAuth.js provides a `getSession()` helper which should be called **client side only** to return the current active session.
|
||||
NextAuth.js provides a `getSession()` method which can be called client or server side to return a session.
|
||||
|
||||
On the server side, **this is still available to use**, however, we recommend using `unstable_getServerSession` going forward. The idea behind this is to avoid an additional unnecessary `fetch` call on the server side. For more information, please check out [this issue](https://github.com/nextauthjs/next-auth/issues/1535).
|
||||
It calls `/api/auth/session` and returns a promise with a session object, or null if no session exists.
|
||||
|
||||
:::note
|
||||
The `unstable_getServerSession` only has the prefix `unstable_` at the moment, because the API may change in the future. There are no known bugs at the moment and it is safe to use. If you discover any issues, please do report them as a [GitHub Issue](https://github.com/nextauthjs/next-auth/issues) and we will patch them as soon as possible.
|
||||
:::
|
||||
|
||||
This helper is helpful in case you want to read the session outside of the context of React.
|
||||
|
||||
When called, `getSession()` will send a request to `/api/auth/session` and returns a promise with a [session object](https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/core/types.ts#L407-L425), or `null` if no session exists.
|
||||
#### Client Side Example
|
||||
|
||||
```js
|
||||
async function myFunction() {
|
||||
@@ -182,7 +176,23 @@ async function myFunction() {
|
||||
}
|
||||
```
|
||||
|
||||
Read the tutorial [securing pages and API routes](/tutorials/securing-pages-and-api-routes) to know how to fetch the session in server side calls using `unstable_getServerSession()`.
|
||||
#### Server Side Example
|
||||
|
||||
```js
|
||||
import { getSession } from "next-auth/react"
|
||||
|
||||
export default async (req, res) => {
|
||||
const session = await getSession({ req })
|
||||
/* ... */
|
||||
res.end()
|
||||
}
|
||||
```
|
||||
|
||||
:::note
|
||||
When calling `getSession()` server side, you need to pass `{req}` or `context` object.
|
||||
:::
|
||||
|
||||
The tutorial [securing pages and API routes](/tutorials/securing-pages-and-api-routes) shows how to use `getSession()` in server side calls.
|
||||
|
||||
---
|
||||
|
||||
@@ -244,7 +254,7 @@ export default async (req, res) => {
|
||||
```
|
||||
|
||||
:::note
|
||||
Unlike and `getCsrfToken()`, when calling `getProviders()` server side, you don't need to pass anything, just as calling it client side.
|
||||
Unlike `getSession()` and `getCsrfToken()`, when calling `getProviders()` server side, you don't need to pass anything, just as calling it client side.
|
||||
:::
|
||||
|
||||
---
|
||||
@@ -426,15 +436,14 @@ If you pass the `session` page prop to the `<SessionProvider>` – as in the exa
|
||||
This only works on pages where you provide the correct `pageProps`, however. This is normally done in `getInitialProps` or `getServerSideProps` on an individual page basis like so:
|
||||
|
||||
```js title="pages/index.js"
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { authOptions } from './api/auth/[...nextauth]'
|
||||
import { getSession } from "next-auth/react"
|
||||
|
||||
...
|
||||
|
||||
export async function getServerSideProps({ req, res }) {
|
||||
export async function getServerSideProps(ctx) {
|
||||
return {
|
||||
props: {
|
||||
session: await unstable_getServerSession(req, res, authOptions)
|
||||
session: await getSession(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -446,7 +455,7 @@ If every one of your pages needs to be protected, you can do this in `getInitial
|
||||
|
||||
The session state is automatically synchronized across all open tabs/windows and they are all updated whenever they gain or lose focus or the state changes (e.g. a user signs in or out) when `refetchOnWindowFocus` is `true`.
|
||||
|
||||
If you have session expiry times of 30 days (the default) or more then you probably don't need to change any of the default options in the Provider. If you need to, you can trigger an update of the session object across all tabs/windows by calling [`getSession()`](/getting-started/client#getsession) from a client side function.
|
||||
If you have session expiry times of 30 days (the default) or more then you probably don't need to change any of the default options in the Provider. If you need to, you can trigger an update of the session object across all tabs/windows by calling `getSession()` from a client side function.
|
||||
|
||||
However, if you need to customize the session behavior and/or are using short session expiry times, you can pass options to the provider to customize the behavior of the `useSession()` hook.
|
||||
|
||||
|
||||
@@ -93,14 +93,13 @@ You can use the `useSession` hook from anywhere in your application (e.g. in a h
|
||||
|
||||
### Backend - API Route
|
||||
|
||||
To protect an API Route, you can use the [`unstable_getServerSession()`](/configuration/nextjs#unstable_getserversession) method.
|
||||
To protect an API Route, you can use the [`getSession()`](/getting-started/client#getsession) method in the NextAuth.js client.
|
||||
|
||||
```javascript title="pages/api/restricted.js" showLineNumbers
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "./api/auth/[...nextauth]"
|
||||
import { getSession } from "next-auth/react"
|
||||
|
||||
export default async (req, res) => {
|
||||
const session = await unstable_getServerSession(req, res, authOptions)
|
||||
const session = await getSession({ req })
|
||||
|
||||
if (session) {
|
||||
res.send({
|
||||
@@ -144,7 +143,7 @@ callbacks: {
|
||||
...
|
||||
```
|
||||
|
||||
Now whenever you call [`getSession`](/getting-started/client#getsession) or [`useSession`](/getting-started/client#usesession), the data object which is returned will include the `accessToken` value.
|
||||
Now whenever you call `getSession` or `useSession`, the data object which is returned will include the `accessToken` value.
|
||||
|
||||
```jsx title="components/accessToken.jsx" showLineNumbers
|
||||
import { useSession, signIn, signOut } from "next-auth/react"
|
||||
|
||||
@@ -13,7 +13,7 @@ We encourage users to try it out and report any and all issues they come across.
|
||||
|
||||
You can upgrade to the new version by running:
|
||||
|
||||
```bash npm2yarn2pnpm
|
||||
```bash npm2yarn
|
||||
npm install next-auth
|
||||
```
|
||||
|
||||
|
||||
@@ -124,74 +124,67 @@ providers: [
|
||||
The following code shows the complete source for the built-in `sendVerificationRequest()` method:
|
||||
|
||||
```js
|
||||
import { createTransport } from "nodemailer"
|
||||
import nodemailer from "nodemailer"
|
||||
|
||||
async function sendVerificationRequest(params) {
|
||||
const { identifier, url, provider, theme } = params
|
||||
async function sendVerificationRequest({
|
||||
identifier: email,
|
||||
url,
|
||||
provider: { server, from },
|
||||
}) {
|
||||
const { host } = new URL(url)
|
||||
// NOTE: You are not required to use `nodemailer`, use whatever you want.
|
||||
const transport = createTransport(provider.server)
|
||||
const result = await transport.sendMail({
|
||||
to: identifier,
|
||||
from: provider.from,
|
||||
const transport = nodemailer.createTransport(server)
|
||||
await transport.sendMail({
|
||||
to: email,
|
||||
from,
|
||||
subject: `Sign in to ${host}`,
|
||||
text: text({ url, host }),
|
||||
html: html({ url, host, theme }),
|
||||
html: html({ url, host, email }),
|
||||
})
|
||||
const failed = result.rejected.concat(result.pending).filter(Boolean)
|
||||
if (failed.length) {
|
||||
throw new Error(`Email(s) (${failed.join(", ")}) could not be sent`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Email HTML body
|
||||
* Insert invisible space into domains from being turned into a hyperlink by email
|
||||
* clients like Outlook and Apple mail, as this is confusing because it seems
|
||||
* like they are supposed to click on it to sign in.
|
||||
*
|
||||
* @note We don't add the email address to avoid needing to escape it, if you do, remember to sanitize it!
|
||||
*/
|
||||
function html(params: { url: string; host: string; theme: Theme }) {
|
||||
const { url, host, theme } = params
|
||||
// Email HTML body
|
||||
function html({ url, host, email }: Record<"url" | "host" | "email", string>) {
|
||||
// Insert invisible space into domains and email address to prevent both the
|
||||
// email address and the domain from being turned into a hyperlink by email
|
||||
// clients like Outlook and Apple mail, as this is confusing because it seems
|
||||
// like they are supposed to click on their email address to sign in.
|
||||
const escapedEmail = `${email.replace(/\./g, "​.")}`
|
||||
const escapedHost = `${host.replace(/\./g, "​.")}`
|
||||
|
||||
const escapedHost = host.replace(/\./g, "​.")
|
||||
|
||||
const brandColor = theme.brandColor || "#346df1"
|
||||
const color = {
|
||||
background: "#f9f9f9",
|
||||
text: "#444",
|
||||
mainBackground: "#fff",
|
||||
buttonBackground: brandColor,
|
||||
buttonBorder: brandColor,
|
||||
buttonText: theme.buttonText || "#fff",
|
||||
}
|
||||
// Some simple styling options
|
||||
const backgroundColor = "#f9f9f9"
|
||||
const textColor = "#444444"
|
||||
const mainBackgroundColor = "#ffffff"
|
||||
const buttonBackgroundColor = "#346df1"
|
||||
const buttonBorderColor = "#346df1"
|
||||
const buttonTextColor = "#ffffff"
|
||||
|
||||
return `
|
||||
<body style="background: ${color.background};">
|
||||
<table width="100%" border="0" cellspacing="20" cellpadding="0"
|
||||
style="background: ${color.mainBackground}; max-width: 600px; margin: auto; border-radius: 10px;">
|
||||
<body style="background: ${backgroundColor};">
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td align="center"
|
||||
style="padding: 10px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
|
||||
Sign in to <strong>${escapedHost}</strong>
|
||||
<td align="center" style="padding: 10px 0px 20px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
|
||||
<strong>${escapedHost}</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table width="100%" border="0" cellspacing="20" cellpadding="0" style="background: ${mainBackgroundColor}; max-width: 600px; margin: auto; border-radius: 10px;">
|
||||
<tr>
|
||||
<td align="center" style="padding: 10px 0px 0px 0px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
|
||||
Sign in as <strong>${escapedEmail}</strong>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="padding: 20px 0;">
|
||||
<table border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td align="center" style="border-radius: 5px;" bgcolor="${color.buttonBackground}"><a href="${url}"
|
||||
target="_blank"
|
||||
style="font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${color.buttonText}; text-decoration: none; border-radius: 5px; padding: 10px 20px; border: 1px solid ${color.buttonBorder}; display: inline-block; font-weight: bold;">Sign
|
||||
in</a></td>
|
||||
<td align="center" style="border-radius: 5px;" bgcolor="${buttonBackgroundColor}"><a href="${url}" target="_blank" style="font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${buttonTextColor}; text-decoration: none; border-radius: 5px; padding: 10px 20px; border: 1px solid ${buttonBorderColor}; display: inline-block; font-weight: bold;">Sign in</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"
|
||||
style="padding: 0px 0px 10px 0px; font-size: 16px; line-height: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
|
||||
<td align="center" style="padding: 0px 0px 10px 0px; font-size: 16px; line-height: 22px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
|
||||
If you did not request this email you can safely ignore it.
|
||||
</td>
|
||||
</tr>
|
||||
@@ -200,8 +193,8 @@ function html(params: { url: string; host: string; theme: Theme }) {
|
||||
`
|
||||
}
|
||||
|
||||
/** Email Text body (fallback for email clients that don't render HTML, e.g. feature phones) */
|
||||
function text({ url, host }: { url: string; host: string }) {
|
||||
// Email Text body (fallback for email clients that don't render HTML, e.g. feature phones)
|
||||
function text({ url, host }: Record<"url" | "host", string>) {
|
||||
return `Sign in to ${host}\n${url}\n\n`
|
||||
}
|
||||
```
|
||||
|
||||
@@ -30,8 +30,8 @@ import GitHubProvider from "next-auth/providers/github";
|
||||
...
|
||||
providers: [
|
||||
GitHubProvider({
|
||||
clientId: process.env.GITHUB_ID,
|
||||
clientSecret: process.env.GITHUB_SECRET
|
||||
clientId: process.env.GITHUB_CLIENT_ID,
|
||||
clientSecret: process.env.GITHUB_CLIENT_SECRET
|
||||
})
|
||||
]
|
||||
...
|
||||
|
||||
@@ -5,7 +5,7 @@ title: Overview
|
||||
|
||||
Authentication Providers in **NextAuth.js** are services that can be used to sign in a user.
|
||||
|
||||
There are four ways a user can be signed in:
|
||||
There's four ways a user can be signed in:
|
||||
|
||||
- [Using a built-in OAuth Provider](/configuration/providers/oauth) (e.g Github, Twitter, Google, etc...)
|
||||
- [Using a custom OAuth Provider](/configuration/providers/oauth#using-a-custom-provider)
|
||||
|
||||
@@ -13,7 +13,7 @@ https://api.slack.com/docs/sign-in-with-slack
|
||||
https://api.slack.com/apps
|
||||
|
||||
:::warning
|
||||
Slack requires that the redirect URL of your app uses `https`, even for local development. An easy workaround for this is using a service like [`ngrok`](https://ngrok.com) that creates a secure tunnel to your app, using `https`. Remember to set the url as `NEXTAUTH_URL` as well.
|
||||
Slack requires you that the redirect URL of your app uses `https`, even for local development. An easy workaround for this is using a service like [`ngrok`](https://ngrok.com) that creates a secure tunnel to your app, using `https`. Remember to set the url as `NEXTAUTH_URL` as well.
|
||||
:::
|
||||
|
||||

|
||||
|
||||
@@ -7,7 +7,7 @@ NextAuth.js provides the ability to setup a [custom Credential provider](/config
|
||||
|
||||
You will need an additional dependency, `ldapjs`, which you can install by running
|
||||
|
||||
```bash npm2yarn2pnpm
|
||||
```bash npm2yarn
|
||||
npm install ldapjs
|
||||
```
|
||||
|
||||
|
||||
@@ -40,29 +40,12 @@ export default function Page() {
|
||||
}
|
||||
```
|
||||
|
||||
### Next.js (Middleware)
|
||||
|
||||
With NextAuth.js 4.2.0 and Next.js 12, you can now protect your pages via the middleware pattern more easily. If you would like to protect all pages, you can create a `_middleware.js` file in your root `pages` directory which looks like this.
|
||||
|
||||
```js title="/pages/_middleware.js"
|
||||
export { default } from "next-auth/middleware"
|
||||
```
|
||||
|
||||
Otherwise, if you only want to protect a subset of pages, you could put it in a subdirectory as well, for example in `/pages/admin/_middleware.js` would protect all pages under `/admin`.
|
||||
|
||||
For the time being, the `withAuth` middleware only supports `"jwt"` as [session strategy](https://next-auth.js.org/configuration/options#session).
|
||||
|
||||
More details can be found [here](https://next-auth.js.org/configuration/nextjs#middleware).
|
||||
|
||||
### Server Side
|
||||
|
||||
You can protect server side rendered pages using the `unstable_getServerSession` method. This is different from the old `getSession()` method, in that it does not do an extra fetch out over the internet to confirm data from itself, increasing performance significantly.
|
||||
|
||||
You need to add this to every server rendered page you want to protect. Be aware, `unstable_getServerSession` takes slightly different arguments than the method it is replacing, `getSession`.
|
||||
You can protect server side rendered pages using the `getSession()` method.
|
||||
|
||||
```js title="pages/server-side-example.js"
|
||||
import { useSession, unstable_getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "./api/auth/[...nextauth]"
|
||||
import { useSession, getSession } from "next-auth/react"
|
||||
|
||||
export default function Page() {
|
||||
const { data: session } = useSession()
|
||||
@@ -83,11 +66,7 @@ export default function Page() {
|
||||
export async function getServerSideProps(context) {
|
||||
return {
|
||||
props: {
|
||||
session: await unstable_getServerSession(
|
||||
context.req,
|
||||
context.res,
|
||||
authOptions
|
||||
),
|
||||
session: await getSession(context),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -115,16 +94,15 @@ export default function App({
|
||||
|
||||
## Securing API Routes
|
||||
|
||||
### Using unstable_getServerSession()
|
||||
### Using getSession()
|
||||
|
||||
You can protect API routes using the `unstable_getServerSession()` method.
|
||||
You can protect API routes using the `getSession()` method.
|
||||
|
||||
```js title="pages/api/get-session-example.js"
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "./api/auth/[...nextauth]"
|
||||
import { getSession } from "next-auth/react"
|
||||
|
||||
export default async (req, res) => {
|
||||
const session = await unstable_getServerSession(req, res, authOptions)
|
||||
const session = await getSession({ req })
|
||||
if (session) {
|
||||
// Signed in
|
||||
console.log("Session", JSON.stringify(session, null, 2))
|
||||
|
||||
@@ -9,7 +9,7 @@ To test an implementation of NextAuth.js, we encourage you to use [Cypress](http
|
||||
|
||||
To get started, install the dependencies:
|
||||
|
||||
```bash npm2yarn2pnpm
|
||||
```bash npm2yarn
|
||||
npm install --save-dev cypress cypress-social-logins @testing-library/cypress
|
||||
```
|
||||
|
||||
|
||||
@@ -33,10 +33,6 @@ In development, we generate a `secret` based on your configuration for convenien
|
||||
|
||||
Twitter OAuth 2.0 is currently in beta as certain changes might still be necessary. This is not covered by semver. See the docs https://next-auth.js.org/providers/twitter#oauth-2
|
||||
|
||||
#### EXPERIMENTAL_API
|
||||
|
||||
Some APIs are still experimental; they may be changed or removed in the future. Use at your own risk.
|
||||
|
||||
## Adapter
|
||||
|
||||
### ADAPTER_TYPEORM_UPDATING_ENTITIES
|
||||
|
||||
@@ -155,9 +155,9 @@ module.exports = {
|
||||
showLastUpdateAuthor: true,
|
||||
showLastUpdateTime: true,
|
||||
remarkPlugins: [
|
||||
require("@sapphire/docusaurus-plugin-npm2yarn2pnpm").npm2yarn2pnpm,
|
||||
require("remark-github"),
|
||||
require("mdx-mermaid"),
|
||||
[require("@docusaurus/remark-plugin-npm2yarn"), { sync: true }],
|
||||
],
|
||||
versions: {
|
||||
current: {
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
"generate-providers": "node ./scripts/generate-providers.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^2.0.0-beta.21",
|
||||
"@docusaurus/preset-classic": "^2.0.0-beta.21",
|
||||
"@docusaurus/theme-common": "2.0.0-beta.21",
|
||||
"@docusaurus/core": "^2.0.0-beta.20",
|
||||
"@docusaurus/preset-classic": "^2.0.0-beta.20",
|
||||
"@docusaurus/remark-plugin-npm2yarn": "^2.0.0-beta.20",
|
||||
"@docusaurus/theme-common": "2.0.0-beta.20",
|
||||
"@mdx-js/react": "1.6.22",
|
||||
"@sapphire/docusaurus-plugin-npm2yarn2pnpm": "^1.1.0",
|
||||
"classnames": "^2.3.1",
|
||||
"mdx-mermaid": "^1.2.2",
|
||||
"mermaid": "^9.0.1",
|
||||
|
||||
@@ -1,23 +1,4 @@
|
||||
{
|
||||
"headers": [
|
||||
{
|
||||
"source": "/(.*)",
|
||||
"headers": [
|
||||
{
|
||||
"key": "X-Content-Type-Options",
|
||||
"value": "nosniff"
|
||||
},
|
||||
{
|
||||
"key": "X-Frame-Options",
|
||||
"value": "DENY"
|
||||
},
|
||||
{
|
||||
"key": "X-XSS-Protection",
|
||||
"value": "1; mode=block"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"redirects": [
|
||||
{
|
||||
"source": "/schemas/models",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
Open Source. Full Stack. Own Your Data.
|
||||
</p>
|
||||
<!-- <p align="center" style="align: center;">
|
||||
<img src="https://github.com/nextauthjs/next-auth/actions/workflows/release.yml/badge.svg?branch=main" alt="CI Test" />
|
||||
<img src="https://github.com/nextauthjs/adapters/actions/workflows/release.yml/badge.svg" alt="CI Test" />
|
||||
<img src="https://img.shields.io/bundlephobia/minzip/@next-auth/prisma-adapter" alt="Bundle Size"/>
|
||||
<img src="https://img.shields.io/npm/v/@next-auth/prisma-adapter" alt="@next-auth/prisma-adapter Version" />
|
||||
</p> -->
|
||||
@@ -150,7 +150,7 @@ type User
|
||||
|
||||
## Contributing
|
||||
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/next-auth/blob/main/CONTRIBUTING.md).
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/adapters/blob/main/CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
"version": "1.0.3",
|
||||
"description": "Dgraph adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"repository": "https://github.com/nextauthjs/adapters",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||
"url": "https://github.com/nextauthjs/adapters/issues"
|
||||
},
|
||||
"author": "Arnaud Derbey <arnaud@derbey.dev>",
|
||||
"contributors": [],
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
Open Source. Full Stack. Own Your Data.
|
||||
</p>
|
||||
<p align="center" style="align: center;">
|
||||
<img src="https://github.com/nextauthjs/next-auth/actions/workflows/release.yml/badge.svg?branch=main" alt="Build Test" />
|
||||
<img src="https://github.com/nextauthjs/adapters/actions/workflows/release.yml/badge.svg" alt="Build Test" />
|
||||
<img src="https://img.shields.io/bundlephobia/minzip/@next-auth/dynamodb-adapter/latest" alt="Bundle Size"/>
|
||||
<img src="https://img.shields.io/npm/v/@next-auth/dynamodb-adapter" alt="@next-auth/dynamodb-adapter Version" />
|
||||
</p>
|
||||
@@ -96,7 +96,7 @@ Here is a schema of the table :
|
||||
|
||||
## Contributing
|
||||
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/next-auth/blob/main/CONTRIBUTING.md).
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/adapters/blob/main/CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-auth/dynamodb-adapter",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"repository": "https://github.com/nextauthjs/adapters",
|
||||
"version": "1.0.3",
|
||||
"description": "AWS DynamoDB adapter for next-auth.",
|
||||
"keywords": [
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
Open Source. Full Stack. Own Your Data.
|
||||
</p>
|
||||
<p align="center" style="align: center;">
|
||||
<img src="https://github.com/nextauthjs/next-auth/actions/workflows/release.yml/badge.svg?branch=main" alt="Build Test" />
|
||||
<img src="https://github.com/nextauthjs/adapters/actions/workflows/release.yml/badge.svg" alt="Build Test" />
|
||||
<a href="https://www.npmjs.com/package/@next-auth/faunadb-adapter" target="_blank"><img src="https://img.shields.io/bundlephobia/minzip/@next-auth/fauna-adapter/next" alt="Bundle Size"/></a>
|
||||
<a href="https://www.npmjs.com/package/@next-auth/faunadb-adapter" target="_blank"><img src="https://img.shields.io/npm/v/@next-auth/fauna-adapter/next" alt="@next-auth/fauna-adapter Version" /></a>
|
||||
</p>
|
||||
@@ -53,7 +53,7 @@ export default NextAuth({
|
||||
|
||||
## Contributing
|
||||
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/next-auth/blob/main/CONTRIBUTING.md).
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/adapters/blob/main/CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "1.0.3",
|
||||
"description": "Fauna Adapter for NextAuth",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"repository": "https://github.com/nextauthjs/adapters",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||
},
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
Open Source. Full Stack. Own Your Data.
|
||||
</p>
|
||||
<p align="center" style="align: center;">
|
||||
<img src="https://github.com/nextauthjs/next-auth/actions/workflows/release.yml/badge.svg?branch=main" alt="Build Test" />
|
||||
<img src="https://github.com/nextauthjs/adapters/actions/workflows/release.yml/badge.svg" alt="Build Test" />
|
||||
<img src="https://img.shields.io/bundlephobia/minzip/@next-auth/firebase-adapter/latest" alt="Bundle Size"/>
|
||||
<img src="https://img.shields.io/npm/v/@next-auth/firebase-adapter" alt="@next-auth/firebase-adapter Version" />
|
||||
</p>
|
||||
@@ -83,7 +83,7 @@ See [firebase.google.com/docs/web/setup](https://firebase.google.com/docs/web/se
|
||||
|
||||
## Contributing
|
||||
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/next-auth/blob/main/CONTRIBUTING.md).
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/adapters/blob/main/CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
"version": "0.1.3",
|
||||
"description": "Firebase adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"repository": "https://github.com/nextauthjs/adapters",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||
"url": "https://github.com/nextauthjs/adapters/issues"
|
||||
},
|
||||
"author": "Ron Houben <ron.houben85@gmail.com>",
|
||||
"contributors": [
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
Open Source. Full Stack. Own Your Data.
|
||||
</p>
|
||||
<p align="center" style="align: center;">
|
||||
<img src="https://github.com/nextauthjs/next-auth/actions/workflows/release.yml/badge.svg?branch=main" alt="CI Test" />
|
||||
<img src="https://github.com/nextauthjs/adapters/actions/workflows/release.yml/badge.svg" alt="CI Test" />
|
||||
<a href="https://www.npmjs.com/package/@next-auth/mikro-orm-adapter" target="_blank"><img src="https://img.shields.io/bundlephobia/minzip/@next-auth/mikro-orm-adapter/next" alt="Bundle Size"/></a>
|
||||
<a href="https://www.npmjs.com/package/@next-auth/mikro-orm-adapter" target="_blank"><img src="https://img.shields.io/npm/v/@next-auth/mikro-orm-adapter/next" alt="@next-auth/mikro-orm-adapter Version" /></a>
|
||||
</p>
|
||||
@@ -49,7 +49,7 @@ This is the MikroORM Adapter for [`next-auth`](https://next-auth.js.org). This p
|
||||
|
||||
## Contributing
|
||||
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/next-auth/blob/main/CONTRIBUTING.md).
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/adapters/blob/main/CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "2.0.1",
|
||||
"description": "MikroORM adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"repository": "https://github.com/nextauthjs/adapters",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
Open Source. Full Stack. Own Your Data.
|
||||
</p>
|
||||
<p align="center" style="align: center;">
|
||||
<img src="https://github.com/nextauthjs/next-auth/actions/workflows/release.yml/badge.svg?branch=main" alt="CI Test" />
|
||||
<img src="https://github.com/nextauthjs/adapters/actions/workflows/release.yml/badge.svg" alt="CI Test" />
|
||||
<a href="https://www.npmjs.com/package/@next-auth/mongodb-adapter" target="_blank"><img src="https://img.shields.io/bundlephobia/minzip/@next-auth/mongodb-adapter" alt="Bundle Size"/></a>
|
||||
<a href="https://www.npmjs.com/package/@next-auth/mongodb-adapter" target="_blank"><img src="https://img.shields.io/npm/v/@next-auth/mongodb-adapter" alt="@next-auth/mongodb-adapter Version" /></a>
|
||||
</p>
|
||||
@@ -79,7 +79,7 @@ export default NextAuth({
|
||||
|
||||
## Contributing
|
||||
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/next-auth/blob/main/CONTRIBUTING.md).
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/adapters/blob/main/CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "1.0.3",
|
||||
"description": "mongoDB adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"repository": "https://github.com/nextauthjs/adapters",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
Open Source. Full Stack. Own Your Data.
|
||||
</p>
|
||||
<p align="center" style="align: center;">
|
||||
<img src="https://github.com/nextauthjs/next-auth/actions/workflows/release.yml/badge.svg?branch=main" alt="Canary CI Test" />
|
||||
<img src="https://github.com/nextauthjs/adapters/actions/workflows/release.yml/badge.svg" alt="Canary CI Test" />
|
||||
<img src="https://img.shields.io/bundlephobia/minzip/@next-auth/neo4j-adapter" alt="Bundle Size"/>
|
||||
<img src="https://img.shields.io/npm/v/@next-auth/neo4j-adapter" alt="@next-auth/neo4j-adapter Version" />
|
||||
</p>
|
||||
@@ -50,7 +50,7 @@ export default NextAuth({
|
||||
|
||||
## Contributing
|
||||
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please first read our [Contributing Guide](https://github.com/nextauthjs/next-auth/blob/canary/CONTRIBUTING.md).
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please first read our [Contributing Guide](https://github.com/nextauthjs/adapters/blob/canary/CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "1.0.3",
|
||||
"description": "neo4j adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"repository": "https://github.com/nextauthjs/adapters",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
Open Source. Full Stack. Own Your Data.
|
||||
</p>
|
||||
<p align="center" style="align: center;">
|
||||
<img src="https://github.com/nextauthjs/next-auth/actions/workflows/release.yml/badge.svg?branch=main" alt="CI Test" />
|
||||
<img src="https://github.com/nextauthjs/adapters/actions/workflows/release.yml/badge.svg" alt="CI Test" />
|
||||
<img src="https://img.shields.io/bundlephobia/minzip/@next-auth/pouchdb-adapter" alt="Bundle Size"/>
|
||||
<img src="https://img.shields.io/npm/v/@next-auth/pouchdb-adapter" alt="@next-auth/pouchdb-adapter Version" />
|
||||
</p>
|
||||
@@ -71,7 +71,7 @@ For more details, please see https://pouchdb.com/api.html#sync
|
||||
|
||||
## Contributing
|
||||
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/next-auth/blob/main/CONTRIBUTING.md).
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/adapters/blob/main/CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "0.1.3",
|
||||
"description": "PouchDB adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"repository": "https://github.com/nextauthjs/adapters",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
Open Source. Full Stack. Own Your Data.
|
||||
</p>
|
||||
<p align="center" style="align: center;">
|
||||
<img src="https://github.com/nextauthjs/next-auth/actions/workflows/release.yml/badge.svg?branch=main" alt="CI Test" />
|
||||
<img src="https://github.com/nextauthjs/adapters/actions/workflows/release.yml/badge.svg" alt="CI Test" />
|
||||
<a href="https://www.npmjs.com/package/@next-auth/prisma-adapter" target="_blank"><img src="https://img.shields.io/bundlephobia/minzip/@next-auth/prisma-adapter/next" alt="Bundle Size"/></a>
|
||||
<a href="https://www.npmjs.com/package/@next-auth/prisma-adapter" target="_blank"><img src="https://img.shields.io/npm/v/@next-auth/prisma-adapter/next" alt="@next-auth/prisma-adapter Version" /></a>
|
||||
</p>
|
||||
@@ -48,7 +48,7 @@ export default NextAuth({
|
||||
|
||||
## Contributing
|
||||
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/next-auth/blob/main/CONTRIBUTING.md).
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/adapters/blob/main/CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "1.0.3",
|
||||
"description": "Prisma adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"repository": "https://github.com/nextauthjs/adapters",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
Open Source. Full Stack. Own Your Data.
|
||||
</p>
|
||||
<p align="center" style="align: center;">
|
||||
<img src="https://github.com/nextauthjs/next-auth/actions/workflows/release.yml/badge.svg?branch=main" alt="CI Test" />
|
||||
<img src="https://github.com/nextauthjs/adapters/actions/workflows/release.yml/badge.svg" alt="CI Test" />
|
||||
<img src="https://img.shields.io/bundlephobia/minzip/@next-auth/sequelize-adapter" alt="Bundle Size"/>
|
||||
<img src="https://img.shields.io/npm/v/@next-auth/sequelize-adapter" alt="@next-auth/sequelize-adapter Version" />
|
||||
</p>
|
||||
@@ -89,7 +89,7 @@ export default NextAuth({
|
||||
|
||||
## Contributing
|
||||
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/next-auth/blob/main/CONTRIBUTING.md).
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/adapters/blob/main/CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
"version": "1.0.4",
|
||||
"description": "Sequelize adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"repository": "https://github.com/nextauthjs/adapters",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||
"url": "https://github.com/nextauthjs/adapters/issues"
|
||||
},
|
||||
"author": "github.com/luke-j",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
Open Source. Full Stack. Own Your Data.
|
||||
</p>
|
||||
<p align="center" style="align: center;">
|
||||
<img src="https://github.com/nextauthjs/next-auth/actions/workflows/release.yml/badge.svg?branch=main" alt="Canary CI Test" />
|
||||
<img src="https://github.com/nextauthjs/adapters/actions/workflows/release.yml/badge.svg" alt="Canary CI Test" />
|
||||
<img src="https://img.shields.io/bundlephobia/minzip/@next-auth/typeorm-legacy-adapter/canary" alt="Bundle Size"/>
|
||||
<img src="https://img.shields.io/npm/v/@next-auth/typeorm-legacy-adapter" alt="@next-auth/typeorm-legacy-adapter Version" />
|
||||
</p>
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
"version": "1.0.3",
|
||||
"description": "TypeORM (legacy) adapter for next-auth.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"repository": "https://github.com/nextauthjs/adapters",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||
"url": "https://github.com/nextauthjs/adapters/issues"
|
||||
},
|
||||
"author": "Iain Collins",
|
||||
"contributors": [
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
Open Source. Full Stack. Own Your Data.
|
||||
</p>
|
||||
<p align="center" style="align: center;">
|
||||
<img src="https://github.com/nextauthjs/next-auth/actions/workflows/release.yml/badge.svg?branch=main" alt="CI Test" />
|
||||
<img src="https://github.com/nextauthjs/adapters/actions/workflows/release.yml/badge.svg" alt="CI Test" />
|
||||
<img src="https://img.shields.io/bundlephobia/minzip/@next-auth/upstash-adapter" alt="Bundle Size"/>
|
||||
<img src="https://img.shields.io/npm/v/@next-auth/upstash-adapter" alt="@next-auth/upstash-adapter Version" />
|
||||
</p>
|
||||
@@ -80,7 +80,7 @@ export default NextAuth({
|
||||
|
||||
## Contributing
|
||||
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/next-auth/blob/main/CONTRIBUTING.md).
|
||||
We're open to all community contributions! If you'd like to contribute in any way, please read our [Contributing Guide](https://github.com/nextauthjs/adapters/blob/main/CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
"version": "3.0.0",
|
||||
"description": "Upstash adapter for next-auth. It uses Upstash's connectionless (HTTP based) Redis client.",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"repository": "https://github.com/nextauthjs/adapters",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||
"url": "https://github.com/nextauthjs/adapters/issues"
|
||||
},
|
||||
"author": "github.com/kay-is",
|
||||
"main": "dist/index.js",
|
||||
@@ -36,7 +36,6 @@
|
||||
"devDependencies": {
|
||||
"@next-auth/adapter-test": "workspace:^0.0.0",
|
||||
"@next-auth/tsconfig": "workspace:^0.0.0",
|
||||
"@types/uuid": "^8.3.3",
|
||||
"@upstash/redis": "^1.0.1",
|
||||
"dotenv": "^10.0.0",
|
||||
"jest": "^27.4.3",
|
||||
|
||||
@@ -166,10 +166,6 @@ export default function App({
|
||||
}
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
If you think you have found a vulnerability (or not sure) in NextAuth.js or any of the related packages (i.e. Adapters), we ask you to have a read of our [Security Policy](https://github.com/nextauthjs/next-auth/blob/main/SECURITY.md) to reach out responsibly. Please do not open Pull Requests/Issues/Discussions before consulting with us.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
[NextAuth.js is made possible thanks to all of its contributors.](https://next-auth.js.org/contributors)
|
||||
|
||||
@@ -25,6 +25,7 @@ module.exports = (api) => {
|
||||
ignore: [
|
||||
"../src/**/__tests__/**",
|
||||
"../src/adapters.ts",
|
||||
"../src/core/types.ts",
|
||||
"../src/providers/oauth-types.ts",
|
||||
],
|
||||
comments: false,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "next-auth",
|
||||
"version": "4.9.0",
|
||||
"version": "4.5.0",
|
||||
"description": "Authentication for Next.js",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth.git",
|
||||
@@ -62,17 +62,16 @@
|
||||
"index.js",
|
||||
"adapters.d.ts",
|
||||
"middleware.d.ts",
|
||||
"middleware.js",
|
||||
"utils"
|
||||
"middleware.js"
|
||||
],
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.16.3",
|
||||
"@panva/hkdf": "^1.0.1",
|
||||
"@panva/oauth4webapi": "^0.0.10",
|
||||
"cookie": "^0.4.1",
|
||||
"jose": "^4.3.7",
|
||||
"oauth": "^0.9.15",
|
||||
"openid-client": "^5.1.0",
|
||||
"preact": "^10.6.3",
|
||||
"preact-render-to-string": "^5.1.19",
|
||||
"uuid": "^8.3.2"
|
||||
@@ -103,7 +102,6 @@
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@testing-library/react-hooks": "^8.0.0",
|
||||
"@testing-library/user-event": "^14.2.0",
|
||||
"@types/jest": "^28.1.3",
|
||||
"@types/node": "^17.0.42",
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"@types/oauth": "^0.9.1",
|
||||
@@ -116,13 +114,13 @@
|
||||
"jest": "^28.1.1",
|
||||
"jest-environment-jsdom": "^28.1.1",
|
||||
"jest-watch-typeahead": "^1.1.0",
|
||||
"msw": "^0.42.3",
|
||||
"next": "12.1.7-canary.51",
|
||||
"msw": "^0.42.1",
|
||||
"next": "^12.1.6",
|
||||
"postcss": "^8.4.14",
|
||||
"postcss-cli": "^9.1.0",
|
||||
"postcss-nested": "^5.0.6",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"whatwg-fetch": "^3.6.2"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -52,7 +52,7 @@ export interface VerificationToken {
|
||||
* - `deleteUser`
|
||||
* - `unlinkAccount`
|
||||
*
|
||||
* [Adapters Overview](https://next-auth.js.org/adapters/overview) |
|
||||
* [Community adapters](https://github.com/nextauthjs/adapters) |
|
||||
* [Create a custom adapter](https://next-auth.js.org/tutorials/creating-a-database-adapter)
|
||||
*/
|
||||
export interface Adapter {
|
||||
|
||||
@@ -9,13 +9,12 @@ import { SessionStore } from "./lib/cookie"
|
||||
import type { NextAuthAction, NextAuthOptions } from "./types"
|
||||
import type { Cookie } from "./lib/cookie"
|
||||
import type { ErrorType } from "./pages/error"
|
||||
import { parse as parseCookie } from "cookie"
|
||||
|
||||
export interface RequestInternal {
|
||||
/** @default "http://localhost:3000" */
|
||||
host?: string
|
||||
method?: string
|
||||
cookies?: Partial<Record<string, string>>
|
||||
cookies?: Record<string, string>
|
||||
headers?: Record<string, any>
|
||||
query?: Record<string, any>
|
||||
body?: Record<string, any>
|
||||
@@ -69,7 +68,7 @@ async function toInternalRequest(
|
||||
method: req.method,
|
||||
headers,
|
||||
body: await getBody(req),
|
||||
cookies: parseCookie(req.headers.get("cookie") ?? ""),
|
||||
cookies: {},
|
||||
providerId: nextauth[1],
|
||||
error: url.searchParams.get("error") ?? nextauth[1],
|
||||
host: detectHost(headers["x-forwarded-host"] ?? headers.host),
|
||||
|
||||
@@ -62,7 +62,6 @@ export async function init({
|
||||
colorScheme: "auto",
|
||||
logo: "",
|
||||
brandColor: "",
|
||||
buttonText: "",
|
||||
},
|
||||
// Custom options override defaults
|
||||
...userOptions,
|
||||
|
||||
@@ -44,7 +44,7 @@ export function assertConfig(
|
||||
): ConfigError | WarningCode | undefined {
|
||||
const { options, req } = params
|
||||
|
||||
// req.query isn't defined when asserting `unstable_getServerSession` for example
|
||||
// req.query isn't defined when asserting `getServerSession` for example
|
||||
if (!req.query?.nextauth && !req.action) {
|
||||
return new MissingAPIRoute(
|
||||
"Cannot find [...nextauth].{js,ts} in `/pages/api/auth`. Make sure the filename is written correctly."
|
||||
|
||||
@@ -155,7 +155,7 @@ export default async function callbackHandler(params: {
|
||||
// If the user is already signed in and the OAuth account isn't already associated
|
||||
// with another user account then we can go ahead and link the accounts safely.
|
||||
await linkAccount({ ...account, userId: user.id })
|
||||
await events.linkAccount?.({ user, account, profile })
|
||||
await events.linkAccount?.({ user, account })
|
||||
|
||||
// As they are already signed in, we don't need to do anything after linking them
|
||||
return { session, user, isNewUser }
|
||||
@@ -205,7 +205,7 @@ export default async function callbackHandler(params: {
|
||||
await events.createUser?.({ user })
|
||||
|
||||
await linkAccount({ ...account, userId: user.id })
|
||||
await events.linkAccount?.({ user, account, profile })
|
||||
await events.linkAccount?.({ user, account })
|
||||
|
||||
session = useJwtSession
|
||||
? {}
|
||||
|
||||
@@ -120,7 +120,7 @@ export class SessionStore {
|
||||
constructor(
|
||||
option: CookieOption,
|
||||
req: {
|
||||
cookies?: Partial<Record<string, string> | Map<string, string>>
|
||||
cookies?: Record<string, string>
|
||||
headers?: Headers | IncomingHttpHeaders | Record<string, string>
|
||||
},
|
||||
logger: LoggerInstance | Console
|
||||
@@ -128,16 +128,11 @@ export class SessionStore {
|
||||
this.#logger = logger
|
||||
this.#option = option
|
||||
|
||||
const { cookies } = req
|
||||
const { name: cookieName } = option
|
||||
if (!req) return
|
||||
|
||||
if (cookies instanceof Map) {
|
||||
for (const name of cookies.keys()) {
|
||||
if (name.startsWith(cookieName)) this.#chunks[name] = cookies.get(name)
|
||||
}
|
||||
} else {
|
||||
for (const name in cookies) {
|
||||
if (name.startsWith(cookieName)) this.#chunks[name] = cookies[name]
|
||||
for (const name in req.cookies) {
|
||||
if (name.startsWith(option.name)) {
|
||||
this.#chunks[name] = req.cookies[name]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ export default async function email(
|
||||
identifier: string,
|
||||
options: InternalOptions<"email">
|
||||
) {
|
||||
const { url, adapter, provider, logger, callbackUrl, theme } = options
|
||||
const { url, adapter, provider, logger, callbackUrl } = options
|
||||
|
||||
// Generate token
|
||||
const token =
|
||||
@@ -42,7 +42,6 @@ export default async function email(
|
||||
expires,
|
||||
url: _url,
|
||||
provider,
|
||||
theme,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error("SEND_VERIFICATION_EMAIL_ERROR", {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { discoveryRequest, processDiscoveryResponse } from "@panva/oauth4webapi"
|
||||
|
||||
import type { AuthorizationServer } from "@panva/oauth4webapi"
|
||||
import type { InternalProvider } from "src/lib/types"
|
||||
|
||||
export default async function getAuthorizationServer(
|
||||
provider: InternalProvider<"oauth">
|
||||
): Promise<AuthorizationServer> {
|
||||
if (provider.idToken) {
|
||||
const issuer = new URL(provider.issuer as string)
|
||||
return await discoveryRequest(issuer).then(
|
||||
async (response) => await processDiscoveryResponse(issuer, response)
|
||||
)
|
||||
} else {
|
||||
return {
|
||||
issuer: provider.issuer as string,
|
||||
authorization_endpoint:
|
||||
typeof provider.authorization === "string"
|
||||
? provider.authorization
|
||||
: provider.authorization?.url,
|
||||
token_endpoint:
|
||||
typeof provider.token === "string"
|
||||
? provider.token
|
||||
: provider.token?.url,
|
||||
userinfo_endpoint:
|
||||
typeof provider.userinfo === "string"
|
||||
? provider.userinfo
|
||||
: provider.userinfo?.url,
|
||||
jwks_uri: provider.jwks_uri,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { openidClient } from "./client"
|
||||
import { oAuth1Client } from "./client-legacy"
|
||||
import { createState } from "./state-handler"
|
||||
import { createPKCE } from "./pkce-handler"
|
||||
import getAuthorizationServer from "./authorization-server"
|
||||
|
||||
import type { AuthorizationParameters } from "openid-client"
|
||||
import type { InternalOptions } from "../../types"
|
||||
@@ -50,28 +50,53 @@ export default async function getAuthorizationUrl(params: {
|
||||
return { redirect: url }
|
||||
}
|
||||
|
||||
const client = await openidClient(options)
|
||||
const authorizationServer = await getAuthorizationServer(provider)
|
||||
|
||||
if (!authorizationServer.authorization_endpoint) throw new Error()
|
||||
const authorizationUrl = new URL(authorizationServer.authorization_endpoint)
|
||||
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
authorizationUrl.searchParams.set(key, value as string)
|
||||
}
|
||||
|
||||
authorizationUrl.searchParams.set("client_id", provider.clientId as string)
|
||||
authorizationUrl.searchParams.set("redirect_uri", provider.callbackUrl)
|
||||
|
||||
if (typeof provider.authorization !== "string" && provider.authorization) {
|
||||
const { params: authorizationEndpointParams } = provider.authorization
|
||||
|
||||
if (typeof authorizationEndpointParams?.response_type === "string") {
|
||||
authorizationUrl.searchParams.set(
|
||||
"response_type",
|
||||
authorizationEndpointParams.response_type
|
||||
)
|
||||
}
|
||||
if (typeof authorizationEndpointParams?.scope === "string") {
|
||||
authorizationUrl.searchParams.set(
|
||||
"scope",
|
||||
authorizationEndpointParams.scope
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const authorizationParams: AuthorizationParameters = params
|
||||
const cookies: Cookie[] = []
|
||||
|
||||
const pkce = await createPKCE(options, authorizationServer)
|
||||
authorizationUrl.searchParams.set("code_challenge", pkce.code_challenge)
|
||||
authorizationUrl.searchParams.set(
|
||||
"code_challenge_method",
|
||||
pkce.code_challenge_method
|
||||
)
|
||||
cookies.push(pkce.cookie)
|
||||
|
||||
const state = await createState(options)
|
||||
if (state) {
|
||||
authorizationParams.state = state.value
|
||||
if (!pkce.isSupported && state) {
|
||||
authorizationUrl.searchParams.set("state", state.value)
|
||||
cookies.push(state.cookie)
|
||||
}
|
||||
|
||||
const pkce = await createPKCE(options)
|
||||
if (pkce) {
|
||||
authorizationParams.code_challenge = pkce.code_challenge
|
||||
authorizationParams.code_challenge_method = pkce.code_challenge_method
|
||||
cookies.push(pkce.cookie)
|
||||
}
|
||||
|
||||
const url = client.authorizationUrl(authorizationParams)
|
||||
|
||||
logger.debug("GET_AUTHORIZATION_URL", { url, cookies })
|
||||
return { redirect: url, cookies }
|
||||
logger.debug("GET_AUTHORIZATION_URL", { authorizationUrl, cookies })
|
||||
return { redirect: authorizationUrl.href, cookies }
|
||||
} catch (error) {
|
||||
logger.error("GET_AUTHORIZATION_URL_ERROR", error as Error)
|
||||
throw error
|
||||
|
||||
@@ -1,16 +1,31 @@
|
||||
import { TokenSet } from "openid-client"
|
||||
import { openidClient } from "./client"
|
||||
import { oAuth1Client } from "./client-legacy"
|
||||
import { useState } from "./state-handler"
|
||||
import { usePKCECodeVerifier } from "./pkce-handler"
|
||||
import { OAuthCallbackError } from "../../errors"
|
||||
import {
|
||||
authorizationCodeGrantRequest,
|
||||
expectNoState,
|
||||
getValidatedIdTokenClaims,
|
||||
isOAuth2Error,
|
||||
processAuthorizationCodeOAuth2Response,
|
||||
processAuthorizationCodeOpenIDResponse,
|
||||
processUserInfoResponse,
|
||||
userInfoRequest,
|
||||
validateAuthResponse,
|
||||
} from "@panva/oauth4webapi"
|
||||
import getAuthorizationServer from "./authorization-server"
|
||||
|
||||
import type { CallbackParamsType } from "openid-client"
|
||||
import type { Account, LoggerInstance, Profile } from "../../.."
|
||||
import type { OAuthChecks, OAuthConfig } from "../../../providers"
|
||||
import type { InternalOptions } from "../../types"
|
||||
import type { RequestInternal, OutgoingResponse } from "../.."
|
||||
import type { Cookie } from "../cookie"
|
||||
import type {
|
||||
OAuth2Error,
|
||||
OAuth2TokenEndpointResponse,
|
||||
OpenIDTokenEndpointResponse,
|
||||
} from "@panva/oauth4webapi"
|
||||
|
||||
export default async function oAuthCallback(params: {
|
||||
options: InternalOptions<"oauth">
|
||||
@@ -19,7 +34,7 @@ export default async function oAuthCallback(params: {
|
||||
method: Required<RequestInternal>["method"]
|
||||
cookies: RequestInternal["cookies"]
|
||||
}): Promise<GetProfileResult & { cookies?: OutgoingResponse["cookies"] }> {
|
||||
const { options, query, body, method, cookies } = params
|
||||
const { options, query, body, cookies } = params
|
||||
const { logger, provider } = options
|
||||
|
||||
const errorMessage = body?.error ?? query?.error
|
||||
@@ -65,81 +80,111 @@ export default async function oAuthCallback(params: {
|
||||
}
|
||||
|
||||
try {
|
||||
const client = await openidClient(options)
|
||||
const client = openidClient(provider)
|
||||
const authorizationServer = await getAuthorizationServer(provider)
|
||||
|
||||
let tokens: TokenSet
|
||||
let tokens:
|
||||
| OpenIDTokenEndpointResponse
|
||||
| OAuth2TokenEndpointResponse
|
||||
| OAuth2Error
|
||||
|
||||
const checks: OAuthChecks = {}
|
||||
let expectedState: string | typeof expectNoState = expectNoState
|
||||
const resCookies: Cookie[] = []
|
||||
const authParams = new URLSearchParams(query)
|
||||
|
||||
const codeVerifier = cookies?.[options.cookies.pkceCodeVerifier.name]
|
||||
const pkce = await usePKCECodeVerifier(
|
||||
codeVerifier,
|
||||
options,
|
||||
authorizationServer
|
||||
)
|
||||
resCookies.push(pkce.cookie)
|
||||
|
||||
const state = await useState(cookies?.[options.cookies.state.name], options)
|
||||
|
||||
if (state) {
|
||||
checks.state = state.value
|
||||
if (!pkce.isSupported && state) {
|
||||
resCookies.push(state.cookie)
|
||||
expectedState = state.value
|
||||
authParams.append("state", state.value)
|
||||
}
|
||||
|
||||
const codeVerifier = cookies?.[options.cookies.pkceCodeVerifier.name]
|
||||
const pkce = await usePKCECodeVerifier(codeVerifier, options)
|
||||
if (pkce) {
|
||||
checks.code_verifier = pkce.codeVerifier
|
||||
resCookies.push(pkce.cookie)
|
||||
const callbackParameters = validateAuthResponse(
|
||||
authorizationServer,
|
||||
client,
|
||||
authParams,
|
||||
expectedState
|
||||
)
|
||||
if (isOAuth2Error(callbackParameters)) {
|
||||
throw new OAuthCallbackError(callbackParameters.error)
|
||||
}
|
||||
|
||||
const params: CallbackParamsType = {
|
||||
...client.callbackParams({
|
||||
url: `http://n?${new URLSearchParams(query)}`,
|
||||
// TODO: Ask to allow object to be passed upstream:
|
||||
// https://github.com/panva/node-openid-client/blob/3ae206dfc78c02134aa87a07f693052c637cab84/types/index.d.ts#L439
|
||||
// @ts-expect-error
|
||||
body,
|
||||
method,
|
||||
}),
|
||||
// @ts-expect-error
|
||||
...provider.token?.params,
|
||||
}
|
||||
const response = await authorizationCodeGrantRequest(
|
||||
authorizationServer,
|
||||
client,
|
||||
callbackParameters,
|
||||
provider.callbackUrl,
|
||||
pkce.codeVerifier
|
||||
)
|
||||
|
||||
// @ts-expect-error
|
||||
if (provider.token?.request) {
|
||||
// @ts-expect-error
|
||||
if (typeof provider.token !== "string" && provider.token?.request) {
|
||||
const params = {
|
||||
...callbackParameters,
|
||||
...provider.token?.params,
|
||||
}
|
||||
const checks = new URLSearchParams()
|
||||
if (state) checks.append("state", state.value)
|
||||
checks.append("code_verifier", pkce.codeVerifier)
|
||||
const response = await provider.token.request({
|
||||
provider,
|
||||
params,
|
||||
checks,
|
||||
client,
|
||||
})
|
||||
tokens = new TokenSet(response.tokens)
|
||||
tokens = response.tokens
|
||||
} else if (provider.idToken) {
|
||||
tokens = await client.callback(provider.callbackUrl, params, checks)
|
||||
tokens = await processAuthorizationCodeOpenIDResponse(
|
||||
authorizationServer,
|
||||
client,
|
||||
response
|
||||
)
|
||||
} else {
|
||||
tokens = await client.oauthCallback(provider.callbackUrl, params, checks)
|
||||
tokens = await processAuthorizationCodeOAuth2Response(
|
||||
authorizationServer,
|
||||
client,
|
||||
response
|
||||
)
|
||||
}
|
||||
|
||||
// REVIEW: How can scope be returned as an array?
|
||||
if (Array.isArray(tokens.scope)) {
|
||||
tokens.scope = tokens.scope.join(" ")
|
||||
if (isOAuth2Error(tokens)) {
|
||||
throw new OAuthCallbackError(tokens.error)
|
||||
}
|
||||
|
||||
let profile: Profile
|
||||
// @ts-expect-error
|
||||
if (provider.userinfo?.request) {
|
||||
// @ts-expect-error
|
||||
let profile: Profile | Response
|
||||
if (typeof provider.userinfo !== "string" && provider.userinfo?.request) {
|
||||
profile = await provider.userinfo.request({
|
||||
provider,
|
||||
tokens,
|
||||
client,
|
||||
})
|
||||
} else if (provider.idToken) {
|
||||
profile = tokens.claims()
|
||||
const idToken = getValidatedIdTokenClaims(tokens)
|
||||
|
||||
profile = await processUserInfoResponse(
|
||||
authorizationServer,
|
||||
client,
|
||||
idToken?.sub as string,
|
||||
response
|
||||
)
|
||||
} else {
|
||||
profile = await client.userinfo(tokens, {
|
||||
// @ts-expect-error
|
||||
params: provider.userinfo?.params,
|
||||
})
|
||||
profile = await userInfoRequest(
|
||||
authorizationServer,
|
||||
client,
|
||||
tokens.access_token
|
||||
)
|
||||
}
|
||||
|
||||
const profileResult = await getProfile({
|
||||
profile,
|
||||
profile: profile as Profile,
|
||||
provider,
|
||||
tokens,
|
||||
logger,
|
||||
@@ -156,7 +201,7 @@ export default async function oAuthCallback(params: {
|
||||
|
||||
export interface GetProfileParams {
|
||||
profile: Profile
|
||||
tokens: TokenSet
|
||||
tokens: OpenIDTokenEndpointResponse | OAuth2TokenEndpointResponse
|
||||
provider: OAuthConfig<any>
|
||||
logger: LoggerInstance
|
||||
}
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import { InternalProvider } from "src/lib/types"
|
||||
import type { Client as WebApiClient } from "@panva/oauth4webapi"
|
||||
import { Issuer, custom } from "openid-client"
|
||||
import type { Client } from "openid-client"
|
||||
import type { InternalOptions } from "../../types"
|
||||
|
||||
/**
|
||||
* NOTE: We can add auto discovery of the provider's endpoint
|
||||
* that requires only one endpoint to be specified by the user.
|
||||
* Check out `Issuer.discover`
|
||||
*
|
||||
* Client supporting OAuth 2.x and OIDC
|
||||
*/
|
||||
export function webApiClient(provider: InternalProvider<"oauth">): WebApiClient {
|
||||
return {
|
||||
client_id: provider.clientId as string,
|
||||
client_secret: provider.clientSecret as string,
|
||||
token_endpoint_auth_method: "client_secret_basic",
|
||||
...provider.client,
|
||||
}
|
||||
}
|
||||
export async function openidClient(
|
||||
options: InternalOptions<"oauth">
|
||||
): Promise<Client> {
|
||||
@@ -31,21 +37,4 @@ export async function openidClient(
|
||||
userinfo_endpoint: provider.userinfo?.url ?? provider.userinfo,
|
||||
})
|
||||
}
|
||||
|
||||
const client = new issuer.Client(
|
||||
{
|
||||
client_id: provider.clientId as string,
|
||||
client_secret: provider.clientSecret as string,
|
||||
redirect_uris: [provider.callbackUrl],
|
||||
...provider.client,
|
||||
},
|
||||
provider.jwks
|
||||
)
|
||||
|
||||
// allow a 10 second skew
|
||||
// See https://github.com/nextauthjs/next-auth/issues/3032
|
||||
// and https://github.com/nextauthjs/next-auth/issues/3067
|
||||
client[custom.clock_tolerance] = 10
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
9
packages/next-auth/src/core/lib/oauth/helper.ts
Normal file
9
packages/next-auth/src/core/lib/oauth/helper.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { generateRandomCodeVerifier } from "@panva/oauth4webapi"
|
||||
|
||||
/**
|
||||
* Generate random `state` value encoded as base64url. This method returns oauth4webapi's `generateRandomCodeVerifier` for convenience.
|
||||
* @see {@link https://github.com/panva/oauth4webapi/blob/main/docs/functions/generateRandomCodeVerifier.md generateRandomCodeVerifier.}
|
||||
*/
|
||||
export function generateRandomState() {
|
||||
return generateRandomCodeVerifier()
|
||||
}
|
||||
@@ -1,30 +1,43 @@
|
||||
import * as jwt from "../../../jwt"
|
||||
import {
|
||||
generateRandomCodeVerifier,
|
||||
calculatePKCECodeChallenge,
|
||||
} from "@panva/oauth4webapi"
|
||||
import { generators } from "openid-client"
|
||||
import type { InternalOptions } from "../../types"
|
||||
import type { Cookie } from "../cookie"
|
||||
import type { AuthorizationServer } from "@panva/oauth4webapi"
|
||||
|
||||
const PKCE_CODE_CHALLENGE_METHOD = "S256"
|
||||
const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
|
||||
|
||||
/**
|
||||
* Check if PKCE is supported by the authorization server.
|
||||
* @see {@link https://datatracker.ietf.org/doc/html/rfc8414#section-2 code_challenge_methods_supported}
|
||||
* @param as The authorization server. @see {@link https://github.com/panva/oauth4webapi/blob/main/docs/interfaces/AuthorizationServer.md AuthorizationServer}
|
||||
*/
|
||||
function isPKCESupported(as: AuthorizationServer) {
|
||||
return !!as.code_challenge_methods_supported?.includes(
|
||||
PKCE_CODE_CHALLENGE_METHOD
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `code_challenge` and `code_challenge_method`
|
||||
* and saves them in a cookie.
|
||||
*/
|
||||
export async function createPKCE(options: InternalOptions<"oauth">): Promise<
|
||||
| undefined
|
||||
| {
|
||||
code_challenge: string
|
||||
code_challenge_method: "S256"
|
||||
cookie: Cookie
|
||||
}
|
||||
> {
|
||||
const { cookies, logger, provider } = options
|
||||
if (!provider.checks?.includes("pkce")) {
|
||||
// Provider does not support PKCE, return nothing.
|
||||
return
|
||||
}
|
||||
const code_verifier = generators.codeVerifier()
|
||||
const code_challenge = generators.codeChallenge(code_verifier)
|
||||
export async function createPKCE(
|
||||
options: InternalOptions<"oauth">,
|
||||
as: AuthorizationServer
|
||||
): Promise<{
|
||||
code_challenge: string
|
||||
code_challenge_method: "S256"
|
||||
cookie: Cookie
|
||||
isSupported: boolean
|
||||
}> {
|
||||
const { cookies, logger } = options
|
||||
const code_verifier = generateRandomCodeVerifier()
|
||||
const code_challenge = await calculatePKCECodeChallenge(code_verifier)
|
||||
|
||||
const expires = new Date()
|
||||
expires.setTime(expires.getTime() + PKCE_MAX_AGE * 1000)
|
||||
@@ -51,34 +64,37 @@ export async function createPKCE(options: InternalOptions<"oauth">): Promise<
|
||||
value: encryptedCodeVerifier,
|
||||
options: { ...cookies.pkceCodeVerifier.options, expires },
|
||||
},
|
||||
isSupported: isPKCESupported(as),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns code_verifier if provider uses PKCE,
|
||||
* Returns `code_verifier`,
|
||||
* and clears the container cookie afterwards.
|
||||
*/
|
||||
export async function usePKCECodeVerifier(
|
||||
codeVerifier: string | undefined,
|
||||
options: InternalOptions<"oauth">
|
||||
): Promise<{ codeVerifier: string; cookie: Cookie } | undefined> {
|
||||
const { cookies, provider } = options
|
||||
options: InternalOptions<"oauth">,
|
||||
as: AuthorizationServer
|
||||
): Promise<{ codeVerifier: string; cookie: Cookie; isSupported: boolean }> {
|
||||
if (codeVerifier === undefined) throw new Error("Invalid code verifier")
|
||||
|
||||
if (!provider?.checks?.includes("pkce") || !codeVerifier) {
|
||||
return
|
||||
}
|
||||
const { cookies } = options
|
||||
|
||||
const pkce = (await jwt.decode({
|
||||
const pkce = await jwt.decode({
|
||||
...options.jwt,
|
||||
token: codeVerifier,
|
||||
})) as any
|
||||
})
|
||||
|
||||
if (pkce === null) throw new Error("Invalid code verifier")
|
||||
|
||||
return {
|
||||
codeVerifier: pkce?.code_verifier ?? undefined,
|
||||
codeVerifier: pkce.code_verifier as string,
|
||||
cookie: {
|
||||
name: cookies.pkceCodeVerifier.name,
|
||||
value: "",
|
||||
options: { ...cookies.pkceCodeVerifier.options, maxAge: 0 },
|
||||
},
|
||||
isSupported: isPKCESupported(as),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { generators } from "openid-client"
|
||||
|
||||
import type { InternalOptions } from "../../types"
|
||||
import type { Cookie } from "../cookie"
|
||||
import { generateRandomState } from "./helper"
|
||||
|
||||
const STATE_MAX_AGE = 60 * 15 // 15 minutes in seconds
|
||||
|
||||
@@ -16,7 +17,7 @@ export async function createState(
|
||||
return
|
||||
}
|
||||
|
||||
const state = generators.state()
|
||||
const state = generateRandomState()
|
||||
|
||||
const encodedState = await jwt.encode({
|
||||
...jwt,
|
||||
|
||||
@@ -30,16 +30,12 @@ export default async function signin(params: {
|
||||
return { redirect: `${url}/error?error=OAuthSignin` }
|
||||
}
|
||||
} else if (provider.type === "email") {
|
||||
/**
|
||||
* @note Technically the part of the email address local mailbox element
|
||||
* (everything before the @ symbol) should be treated as 'case sensitive'
|
||||
* according to RFC 2821, but in practice this causes more problems than
|
||||
* it solves. We treat email addresses as all lower case. If anyone
|
||||
* complains about this we can make strict RFC 2821 compliance an option.
|
||||
*/
|
||||
const email = body?.email?.toLowerCase()
|
||||
|
||||
if (!email) return { redirect: `${url}/error?error=EmailSignin` }
|
||||
// Note: Technically the part of the email address local mailbox element
|
||||
// (everything before the @ symbol) should be treated as 'case sensitive'
|
||||
// according to RFC 2821, but in practice this causes more problems than
|
||||
// it solves. We treat email addresses as all lower case. If anyone
|
||||
// complains about this we can make strict RFC 2821 compliance an option.
|
||||
const email = body?.email?.toLowerCase() ?? null
|
||||
|
||||
// Verified in `assertConfig`
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
@@ -11,10 +11,11 @@ import type { TokenSetParameters } from "openid-client"
|
||||
import type { JWT, JWTOptions } from "../jwt"
|
||||
import type { LoggerInstance } from "../utils/logger"
|
||||
import type { CookieSerializeOptions } from "cookie"
|
||||
import type { TokenEndpointResponse } from "@panva/oauth4webapi"
|
||||
|
||||
import type { NextApiRequest, NextApiResponse } from "next"
|
||||
|
||||
import type { InternalUrl } from "../utils/parse-url"
|
||||
import { InternalUrl } from "../utils/parse-url"
|
||||
|
||||
export type Awaitable<T> = T | PromiseLike<T>
|
||||
|
||||
@@ -117,7 +118,7 @@ export interface NextAuthOptions {
|
||||
* * **Required**: *No*
|
||||
*
|
||||
* [Documentation](https://next-auth.js.org/configuration/options#adapter) |
|
||||
* [Adapters Overview](https://next-auth.js.org/adapters/overview)
|
||||
* [Community adapters](https://github.com/nextauthjs/adapters)
|
||||
*/
|
||||
adapter?: Adapter
|
||||
/**
|
||||
@@ -217,7 +218,6 @@ export interface Theme {
|
||||
colorScheme: "auto" | "dark" | "light"
|
||||
logo?: string
|
||||
brandColor?: string
|
||||
buttonText?: string
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -225,7 +225,7 @@ export interface Theme {
|
||||
* Some of them are available with different casing,
|
||||
* but they refer to the same value.
|
||||
*/
|
||||
export type TokenSet = TokenSetParameters
|
||||
export type TokenSet = TokenEndpointResponse
|
||||
|
||||
/**
|
||||
* Usually contains information about the provider being used
|
||||
@@ -390,11 +390,7 @@ export interface EventCallbacks {
|
||||
signOut: (message: { session: Session; token: JWT }) => Awaitable<void>
|
||||
createUser: (message: { user: User }) => Awaitable<void>
|
||||
updateUser: (message: { user: User }) => Awaitable<void>
|
||||
linkAccount: (message: {
|
||||
user: User
|
||||
account: Account
|
||||
profile: User
|
||||
}) => Awaitable<void>
|
||||
linkAccount: (message: { user: User; account: Account }) => Awaitable<void>
|
||||
/**
|
||||
* The message object will contain one of these depending on
|
||||
* if you use JWT or database persisted sessions:
|
||||
|
||||
@@ -67,7 +67,7 @@ function NextAuth(
|
||||
options: NextAuthOptions
|
||||
): any
|
||||
|
||||
/** The main entry point to next-auth */
|
||||
/** Tha main entry point to next-auth */
|
||||
function NextAuth(
|
||||
...args:
|
||||
| [NextAuthOptions]
|
||||
@@ -83,44 +83,26 @@ function NextAuth(
|
||||
|
||||
export default NextAuth
|
||||
|
||||
let experimentalWarningShown = false
|
||||
export async function unstable_getServerSession(
|
||||
...args:
|
||||
| [
|
||||
GetServerSidePropsContext["req"],
|
||||
GetServerSidePropsContext["res"],
|
||||
NextAuthOptions
|
||||
]
|
||||
| [NextApiRequest, NextApiResponse, NextAuthOptions]
|
||||
export async function getServerSession(
|
||||
context:
|
||||
| GetServerSidePropsContext
|
||||
| { req: NextApiRequest; res: NextApiResponse },
|
||||
options: NextAuthOptions
|
||||
): Promise<Session | null> {
|
||||
if (!experimentalWarningShown && process.env.NODE_ENV !== "production") {
|
||||
console.warn(
|
||||
"[next-auth][warn][EXPERIMENTAL_API]",
|
||||
"\n`unstable_getServerSession` is experimental and may be removed or changed in the future, as the name suggested.",
|
||||
`\nhttps://next-auth.js.org/configuration/nextjs#unstable_getServerSession}`,
|
||||
`\nhttps://next-auth.js.org/warnings#EXPERIMENTAL_API`
|
||||
)
|
||||
experimentalWarningShown = true
|
||||
}
|
||||
|
||||
const [req, res, options] = args
|
||||
|
||||
options.secret = options.secret ?? process.env.NEXTAUTH_SECRET
|
||||
|
||||
const session = await NextAuthHandler<Session | {}>({
|
||||
options,
|
||||
req: {
|
||||
host: detectHost(req.headers["x-forwarded-host"]),
|
||||
host: detectHost(context.req.headers["x-forwarded-host"]),
|
||||
action: "session",
|
||||
method: "GET",
|
||||
cookies: req.cookies,
|
||||
headers: req.headers,
|
||||
cookies: context.req.cookies,
|
||||
headers: context.req.headers,
|
||||
},
|
||||
})
|
||||
|
||||
const { body, cookies } = session
|
||||
|
||||
cookies?.forEach((cookie) => setCookie(res, cookie))
|
||||
cookies?.forEach((cookie) => setCookie(context.res, cookie))
|
||||
|
||||
if (body && Object.keys(body).length) return body as Session
|
||||
return null
|
||||
|
||||
@@ -66,7 +66,7 @@ export interface NextAuthMiddlewareOptions {
|
||||
* @example
|
||||
*
|
||||
* ```js
|
||||
* // `middleware.js`
|
||||
* // `pages/admin/_middleware.js`
|
||||
* import { withAuth } from "next-auth/middleware"
|
||||
*
|
||||
* export default withAuth({
|
||||
@@ -74,9 +74,6 @@ export interface NextAuthMiddlewareOptions {
|
||||
* authorized: ({ token }) => token?.user.isAdmin
|
||||
* }
|
||||
* })
|
||||
*
|
||||
* export const config = { matcher: ["/admin"] }
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* ---
|
||||
@@ -152,7 +149,7 @@ export type WithAuthArgs =
|
||||
* @example
|
||||
*
|
||||
* ```js
|
||||
* // `middleware.js`
|
||||
* // `pages/_middleware.js`
|
||||
* export { default } from "next-auth/middleware"
|
||||
* ```
|
||||
*
|
||||
|
||||
@@ -3,16 +3,6 @@ import { createTransport } from "nodemailer"
|
||||
import type { CommonProviderOptions } from "."
|
||||
import type { Options as SMTPConnectionOptions } from "nodemailer/lib/smtp-connection"
|
||||
import type { Awaitable } from ".."
|
||||
import type { Theme } from "../core/types"
|
||||
|
||||
export interface SendVerificationRequestParams {
|
||||
identifier: string
|
||||
url: string
|
||||
expires: Date
|
||||
provider: EmailConfig
|
||||
token: string
|
||||
theme: Theme
|
||||
}
|
||||
|
||||
export interface EmailConfig extends CommonProviderOptions {
|
||||
type: "email"
|
||||
@@ -26,10 +16,13 @@ export interface EmailConfig extends CommonProviderOptions {
|
||||
* @default 86400
|
||||
*/
|
||||
maxAge?: number
|
||||
/** [Documentation](https://next-auth.js.org/providers/email#customizing-emails) */
|
||||
sendVerificationRequest: (
|
||||
params: SendVerificationRequestParams
|
||||
) => Awaitable<void>
|
||||
sendVerificationRequest: (params: {
|
||||
identifier: string
|
||||
url: string
|
||||
expires: Date
|
||||
provider: EmailConfig
|
||||
token: string
|
||||
}) => Awaitable<void>
|
||||
/**
|
||||
* By default, we are generating a random verification token.
|
||||
* You can make it predictable or modify it as you like with this method.
|
||||
@@ -63,81 +56,78 @@ export default function Email(options: EmailUserConfig): EmailConfig {
|
||||
type: "email",
|
||||
name: "Email",
|
||||
// Server can be an SMTP connection string or a nodemailer config object
|
||||
server: { host: "localhost", port: 25, auth: { user: "", pass: "" } },
|
||||
server: {
|
||||
host: "localhost",
|
||||
port: 25,
|
||||
auth: {
|
||||
user: "",
|
||||
pass: "",
|
||||
},
|
||||
},
|
||||
from: "NextAuth <no-reply@example.com>",
|
||||
maxAge: 24 * 60 * 60,
|
||||
async sendVerificationRequest(params) {
|
||||
const { identifier, url, provider, theme } = params
|
||||
async sendVerificationRequest({
|
||||
identifier: email,
|
||||
url,
|
||||
provider: { server, from },
|
||||
}) {
|
||||
const { host } = new URL(url)
|
||||
const transport = createTransport(provider.server)
|
||||
const result = await transport.sendMail({
|
||||
to: identifier,
|
||||
from: provider.from,
|
||||
const transport = createTransport(server)
|
||||
await transport.sendMail({
|
||||
to: email,
|
||||
from,
|
||||
subject: `Sign in to ${host}`,
|
||||
text: text({ url, host }),
|
||||
html: html({ url, host, theme }),
|
||||
html: html({ url, host, email }),
|
||||
})
|
||||
const failed = result.rejected.concat(result.pending).filter(Boolean)
|
||||
if (failed.length) {
|
||||
throw new Error(`Email(s) (${failed.join(", ")}) could not be sent`)
|
||||
}
|
||||
},
|
||||
options,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Email HTML body
|
||||
* Insert invisible space into domains from being turned into a hyperlink by email
|
||||
* clients like Outlook and Apple mail, as this is confusing because it seems
|
||||
* like they are supposed to click on it to sign in.
|
||||
*
|
||||
* @note We don't add the email address to avoid needing to escape it, if you do, remember to sanitize it!
|
||||
*/
|
||||
function html(params: { url: string; host: string; theme: Theme }) {
|
||||
const { url, host, theme } = params
|
||||
// Email HTML body
|
||||
function html({ url, host, email }: Record<"url" | "host" | "email", string>) {
|
||||
// Insert invisible space into domains and email address to prevent both the
|
||||
// email address and the domain from being turned into a hyperlink by email
|
||||
// clients like Outlook and Apple mail, as this is confusing because it seems
|
||||
// like they are supposed to click on their email address to sign in.
|
||||
const escapedEmail = `${email.replace(/\./g, "​.")}`
|
||||
const escapedHost = `${host.replace(/\./g, "​.")}`
|
||||
|
||||
const escapedHost = host.replace(/\./g, "​.")
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
const brandColor = theme.brandColor || "#346df1"
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
const buttonText = theme.buttonText || "#fff"
|
||||
|
||||
const color = {
|
||||
background: "#f9f9f9",
|
||||
text: "#444",
|
||||
mainBackground: "#fff",
|
||||
buttonBackground: brandColor,
|
||||
buttonBorder: brandColor,
|
||||
buttonText,
|
||||
}
|
||||
// Some simple styling options
|
||||
const backgroundColor = "#f9f9f9"
|
||||
const textColor = "#444444"
|
||||
const mainBackgroundColor = "#ffffff"
|
||||
const buttonBackgroundColor = "#346df1"
|
||||
const buttonBorderColor = "#346df1"
|
||||
const buttonTextColor = "#ffffff"
|
||||
|
||||
return `
|
||||
<body style="background: ${color.background};">
|
||||
<table width="100%" border="0" cellspacing="20" cellpadding="0"
|
||||
style="background: ${color.mainBackground}; max-width: 600px; margin: auto; border-radius: 10px;">
|
||||
<body style="background: ${backgroundColor};">
|
||||
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td align="center"
|
||||
style="padding: 10px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
|
||||
Sign in to <strong>${escapedHost}</strong>
|
||||
<td align="center" style="padding: 10px 0px 20px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
|
||||
<strong>${escapedHost}</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table width="100%" border="0" cellspacing="20" cellpadding="0" style="background: ${mainBackgroundColor}; max-width: 600px; margin: auto; border-radius: 10px;">
|
||||
<tr>
|
||||
<td align="center" style="padding: 10px 0px 0px 0px; font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
|
||||
Sign in as <strong>${escapedEmail}</strong>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" style="padding: 20px 0;">
|
||||
<table border="0" cellspacing="0" cellpadding="0">
|
||||
<tr>
|
||||
<td align="center" style="border-radius: 5px;" bgcolor="${color.buttonBackground}"><a href="${url}"
|
||||
target="_blank"
|
||||
style="font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${color.buttonText}; text-decoration: none; border-radius: 5px; padding: 10px 20px; border: 1px solid ${color.buttonBorder}; display: inline-block; font-weight: bold;">Sign
|
||||
in</a></td>
|
||||
<td align="center" style="border-radius: 5px;" bgcolor="${buttonBackgroundColor}"><a href="${url}" target="_blank" style="font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${buttonTextColor}; text-decoration: none; border-radius: 5px; padding: 10px 20px; border: 1px solid ${buttonBorderColor}; display: inline-block; font-weight: bold;">Sign in</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"
|
||||
style="padding: 0px 0px 10px 0px; font-size: 16px; line-height: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
|
||||
<td align="center" style="padding: 0px 0px 10px 0px; font-size: 16px; line-height: 22px; font-family: Helvetica, Arial, sans-serif; color: ${textColor};">
|
||||
If you did not request this email you can safely ignore it.
|
||||
</td>
|
||||
</tr>
|
||||
@@ -146,7 +136,7 @@ function html(params: { url: string; host: string; theme: Theme }) {
|
||||
`
|
||||
}
|
||||
|
||||
/** Email Text body (fallback for email clients that don't render HTML, e.g. feature phones) */
|
||||
function text({ url, host }: { url: string; host: string }) {
|
||||
// Email Text body (fallback for email clients that don't render HTML, e.g. feature phones)
|
||||
function text({ url, host }: Record<"url" | "host", string>) {
|
||||
return `Sign in to ${host}\n${url}\n\n`
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { AuthorizationServer, userInfoRequest } from "@panva/oauth4webapi"
|
||||
import type { OAuthConfig, OAuthUserConfig } from "."
|
||||
|
||||
interface FacebookPictureData {
|
||||
@@ -26,11 +27,17 @@ export default function Facebook<P extends FacebookProfile>(
|
||||
// https://developers.facebook.com/docs/graph-api/reference/user/#fields
|
||||
params: { fields: "id,name,email,picture" },
|
||||
async request({ tokens, client, provider }) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return await client.userinfo(tokens.access_token!, {
|
||||
// @ts-expect-error
|
||||
params: provider.userinfo?.params,
|
||||
// @ts-expect-error
|
||||
const userinfo_endpoint = new URL(provider.userinfo?.url)
|
||||
// @ts-expect-error
|
||||
Object.entries(provider.userinfo?.params).forEach(([key, value]) => {
|
||||
userinfo_endpoint.searchParams.append(key, value as string)
|
||||
})
|
||||
const as: AuthorizationServer = {
|
||||
issuer: options.issuer as string,
|
||||
userinfo_endpoint: userinfo_endpoint.href,
|
||||
}
|
||||
return await userInfoRequest(as, client, tokens.access_token)
|
||||
},
|
||||
},
|
||||
profile(profile: P) {
|
||||
|
||||
@@ -1,33 +1,20 @@
|
||||
import type { CommonProviderOptions } from "../providers"
|
||||
import type { Profile, TokenSet, User, Awaitable } from ".."
|
||||
|
||||
import type {
|
||||
AuthorizationParameters,
|
||||
CallbackParamsType,
|
||||
Issuer,
|
||||
ClientMetadata,
|
||||
IssuerMetadata,
|
||||
OAuthCallbackChecks,
|
||||
OpenIDCallbackChecks,
|
||||
HttpOptions,
|
||||
} from "openid-client"
|
||||
import type { JWK } from "jose"
|
||||
|
||||
type Client = InstanceType<Issuer["Client"]>
|
||||
import type { AuthorizationServer, Client } from "@panva/oauth4webapi"
|
||||
|
||||
export type { OAuthProviderType } from "./oauth-types"
|
||||
|
||||
type ChecksType = "pkce" | "state" | "none"
|
||||
|
||||
export type OAuthChecks = OpenIDCallbackChecks | OAuthCallbackChecks
|
||||
|
||||
type PartialIssuer = Partial<Pick<IssuerMetadata, "jwks_endpoint" | "issuer">>
|
||||
type PartialIssuer = Partial<Pick<AuthorizationServer, "jwks_uri" | "issuer">>
|
||||
|
||||
type UrlParams = Record<string, unknown>
|
||||
|
||||
type EndpointRequest<C, R, P> = (
|
||||
context: C & {
|
||||
/** `openid-client` Client */
|
||||
/** `oauth4webapi` Client */
|
||||
client: Client
|
||||
/** Provider is passed for convenience, ans also contains the `callbackUrl`. */
|
||||
provider: OAuthConfig<P> & {
|
||||
@@ -61,8 +48,7 @@ export type EndpointHandler<
|
||||
R = any
|
||||
> = AdvancedEndpointHandler<P, C, R>
|
||||
|
||||
export type AuthorizationEndpointHandler =
|
||||
EndpointHandler<AuthorizationParameters>
|
||||
export type AuthorizationEndpointHandler = EndpointHandler<UrlParams>
|
||||
|
||||
export type TokenEndpointHandler = EndpointHandler<
|
||||
UrlParams,
|
||||
@@ -71,12 +57,12 @@ export type TokenEndpointHandler = EndpointHandler<
|
||||
* Parameters extracted from the request to the `/api/auth/callback/:providerId` endpoint.
|
||||
* Contains params like `state`.
|
||||
*/
|
||||
params: CallbackParamsType
|
||||
params: URLSearchParams
|
||||
/**
|
||||
* When using this custom flow, make sure to do all the necessary security checks.
|
||||
* Thist object contains parameters you have to match against the request to make sure it is valid.
|
||||
* This object contains parameters you have to match against the request to make sure it is valid.
|
||||
*/
|
||||
checks: OAuthChecks
|
||||
checks: URLSearchParams
|
||||
},
|
||||
{
|
||||
tokens: TokenSet
|
||||
@@ -86,7 +72,7 @@ export type TokenEndpointHandler = EndpointHandler<
|
||||
export type UserinfoEndpointHandler = EndpointHandler<
|
||||
UrlParams,
|
||||
{ tokens: TokenSet },
|
||||
Profile
|
||||
Profile | Response
|
||||
>
|
||||
|
||||
export interface OAuthConfig<P> extends CommonProviderOptions, PartialIssuer {
|
||||
@@ -112,7 +98,7 @@ export interface OAuthConfig<P> extends CommonProviderOptions, PartialIssuer {
|
||||
version?: string
|
||||
profile?: (profile: P, tokens: TokenSet) => Awaitable<User & { id: string }>
|
||||
checks?: ChecksType | ChecksType[]
|
||||
client?: Partial<ClientMetadata>
|
||||
client?: Partial<Client>
|
||||
jwks?: { keys: JWK[] }
|
||||
clientId?: string
|
||||
clientSecret?: string
|
||||
@@ -128,11 +114,6 @@ export interface OAuthConfig<P> extends CommonProviderOptions, PartialIssuer {
|
||||
idToken?: boolean
|
||||
// TODO: only allow for BattleNet
|
||||
region?: string
|
||||
// TODO: only allow for some
|
||||
issuer?: string
|
||||
/** Read more at: https://github.com/panva/node-openid-client/tree/main/docs#customizing-http-requests */
|
||||
httpOptions?: HttpOptions
|
||||
|
||||
/**
|
||||
* The options provided by the user.
|
||||
* We will perform a deep-merge of these values
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user