mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
20 Commits
next-auth@
...
next-auth@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
163d8c66e2 | ||
|
|
5319dca583 | ||
|
|
cd6ccfde89 | ||
|
|
89d91ea282 | ||
|
|
ca3165bd5a | ||
|
|
aa527b37bf | ||
|
|
f3233641d0 | ||
|
|
4bee970775 | ||
|
|
80a4f50be2 | ||
|
|
1f4ffbaefe | ||
|
|
a911b4a40b | ||
|
|
cb0f3e1ae2 | ||
|
|
c194261617 | ||
|
|
5fdd8483d8 | ||
|
|
99f5b9616f | ||
|
|
d8d9ab94cb | ||
|
|
e8827cbf45 | ||
|
|
37c4a813e3 | ||
|
|
6a23ff7126 | ||
|
|
23db0e68dd |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
||||
git config --global user.name "Balázs Orbán"
|
||||
pnpm release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
NPM_TOKEN_PKG: ${{ secrets.NPM_TOKEN_PKG }}
|
||||
NPM_TOKEN_ORG: ${{ secrets.NPM_TOKEN_ORG }}
|
||||
release-pr:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
export { default } from "next-auth/middleware"
|
||||
|
||||
export const config = { matcher: ["/middleware-protected"] }
|
||||
|
||||
// Other ways to use this middleware
|
||||
|
||||
// import withAuth from "next-auth/middleware"
|
||||
@@ -16,21 +16,21 @@
|
||||
},
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@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",
|
||||
"faunadb": "^4.4.1",
|
||||
"next": "^12.1.0",
|
||||
"nodemailer": "^6.7.2",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"@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": "^17.0.37",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"concurrently": "^7.1.0",
|
||||
"prisma": "^3.10.0"
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"concurrently": "^7",
|
||||
"cpx": "^1.5.0",
|
||||
"fake-smtp-server": "^0.8.0",
|
||||
"prisma": "^3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// This is an example of to protect an API route
|
||||
import { getSession } from "next-auth/react"
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
|
||||
export default async (req, res) => {
|
||||
const session = await getSession({ req })
|
||||
const session = await unstable_getServerSession(req, res, options)
|
||||
|
||||
if (session) {
|
||||
res.send({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// This is an example of how to access a session from an API route
|
||||
import { getSession } from "next-auth/react"
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
|
||||
export default async (req, res) => {
|
||||
const session = await getSession({ req })
|
||||
const session = await unstable_getServerSession(req, res, authOptions)
|
||||
res.send(JSON.stringify(session, null, 2))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getSession } from "next-auth/react"
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import Layout from "../components/layout"
|
||||
|
||||
export default function Page() {
|
||||
@@ -11,13 +11,17 @@ export default function Page() {
|
||||
<Layout>
|
||||
<h1>Server Side Rendering</h1>
|
||||
<p>
|
||||
This page uses the universal <strong>getSession()</strong> method in{" "}
|
||||
<strong>getServerSideProps()</strong>.
|
||||
This page uses the <strong>unstable_getServerSession()</strong> method
|
||||
in <strong>getServerSideProps()</strong>.
|
||||
</p>
|
||||
<p>
|
||||
Using <strong>getSession()</strong> in{" "}
|
||||
<strong>getServerSideProps()</strong> is the recommended approach if you
|
||||
need to support Server Side Rendering with authentication.
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
The advantage of Server Side Rendering is this page does not require
|
||||
@@ -35,7 +39,11 @@ export default function Page() {
|
||||
export async function getServerSideProps(context) {
|
||||
return {
|
||||
props: {
|
||||
session: await getSession(context),
|
||||
session: await unstable_getServerSession(
|
||||
contex.req,
|
||||
contex.res,
|
||||
authOptions
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
"dependencies": {
|
||||
"dotenv": "^16.0.0",
|
||||
"gatsby": "next",
|
||||
"next-auth": "^4.2.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"next-auth": "latest",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vercel": "^23.1.2"
|
||||
|
||||
12
apps/example-nextjs/middleware.ts
Normal file
12
apps/example-nextjs/middleware.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
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.0.11-canary.4",
|
||||
"next": "12.1.7-canary.51",
|
||||
"next-auth": "latest",
|
||||
"nodemailer": "^6.6.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"nodemailer": "^6",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^17.0.14",
|
||||
"@types/react": "^17.0.39",
|
||||
"typescript": "^4.5.5"
|
||||
"@types/node": "^17",
|
||||
"@types/react": "^18",
|
||||
"typescript": "^4"
|
||||
},
|
||||
"prettier": {
|
||||
"semi": false
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Layout from "../../components/layout"
|
||||
import Layout from "../components/layout"
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
@@ -1,8 +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: ({ token }) => token?.userRole === "admin",
|
||||
},
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
import NextAuth from "next-auth"
|
||||
import NextAuth, { NextAuthOptions } 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 default NextAuth({
|
||||
export const authOptions: NextAuthOptions = {
|
||||
// https://next-auth.js.org/configuration/providers/oauth
|
||||
providers: [
|
||||
/* EmailProvider({
|
||||
@@ -18,7 +18,7 @@ export default NextAuth({
|
||||
}),
|
||||
// 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,4 +60,6 @@ export default NextAuth({
|
||||
return token
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export default NextAuth(authOptions)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// This is an example of to protect an API route
|
||||
import { getSession } from "next-auth/react"
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import type { NextApiRequest, NextApiResponse } from "next"
|
||||
|
||||
export default async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session = await getSession({ req })
|
||||
const session = await unstable_getServerSession(req, res, authOptions)
|
||||
|
||||
if (session) {
|
||||
res.send({
|
||||
@@ -12,7 +12,8 @@ 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.",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,2 +0,0 @@
|
||||
// More on how NextAuth.js middleware works: https://next-auth.js.org/configuration/nextjs#middleware
|
||||
export { default } from "next-auth/middleware"
|
||||
@@ -1,26 +1,24 @@
|
||||
import { useSession, getSession } from "next-auth/react"
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "./api/auth/[...nextauth]"
|
||||
import Layout from "../components/layout"
|
||||
import type { NextPageContext } from "next"
|
||||
|
||||
export default function ServerSidePage() {
|
||||
export default function ServerSidePage({ session }) {
|
||||
// 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 universal <strong>getSession()</strong> method in{" "}
|
||||
<strong>getServerSideProps()</strong>.
|
||||
This page uses the <strong>unstable_getServerSession()</strong> method
|
||||
in <strong>unstable_getServerSideProps()</strong>.
|
||||
</p>
|
||||
<p>
|
||||
Using <strong>getSession()</strong> in{" "}
|
||||
<strong>getServerSideProps()</strong> is the recommended approach if you
|
||||
need to support Server Side Rendering with authentication.
|
||||
Using <strong>unstable_getServerSession()</strong> in{" "}
|
||||
<strong>unstable_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
|
||||
@@ -38,7 +36,7 @@ export default function ServerSidePage() {
|
||||
export async function getServerSideProps(context: NextPageContext) {
|
||||
return {
|
||||
props: {
|
||||
session: await getSession(context),
|
||||
session: await unstable_getServerSession(context.req, context.res, authOptions),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"cookie": "0.4.1",
|
||||
"next-auth": "^4.5.0"
|
||||
"next-auth": "workspace:*"
|
||||
},
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
|
||||
@@ -236,7 +236,7 @@ export default NextAuth({
|
||||
encode: async ({ secret, token }) => {
|
||||
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 }) => {
|
||||
|
||||
@@ -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()`, `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()`, `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.
|
||||
|
||||
- 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,6 +53,7 @@ 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
|
||||
|
||||
|
||||
@@ -86,50 +86,27 @@ 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
|
||||
import withAuth from "next-auth/middleware"
|
||||
// or
|
||||
import { withAuth } from "next-auth/middleware"
|
||||
export { default } from "next-auth/middleware"
|
||||
```
|
||||
|
||||
### Custom JWT decode method
|
||||
That's it! Your application is now secured. 🎉
|
||||
|
||||
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.
|
||||
If you only want to secure certain pages, export a `config` object with a `matcher`:
|
||||
|
||||
`[...nextauth].ts`
|
||||
```ts
|
||||
import jwt from "jsonwebtoken";
|
||||
```js
|
||||
export { default } from "next-auth/middleware"
|
||||
|
||||
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;
|
||||
},
|
||||
},
|
||||
})
|
||||
export const config = { matcher: ["/dashboard"] }
|
||||
```
|
||||
|
||||
Any `_middleware.ts`
|
||||
```ts
|
||||
import withAuth from "next-auth/middleware"
|
||||
import jwt from "jsonwebtoken";
|
||||
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.
|
||||
|
||||
export default withAuth({
|
||||
jwt: {
|
||||
decode: async ({ secret, token }) => {
|
||||
return jwt.verify(token, secret) as any;
|
||||
},
|
||||
},
|
||||
callbacks: {
|
||||
authorized: ({ token }) => !!token,
|
||||
},
|
||||
})
|
||||
```
|
||||
---
|
||||
### `callbacks`
|
||||
|
||||
@@ -172,46 +149,24 @@ See the documentation for the [pages option](/configuration/pages) for more info
|
||||
|
||||
---
|
||||
|
||||
### Examples
|
||||
### Advanced usage
|
||||
|
||||
`withAuth` is very flexible, there are multiple ways to use it.
|
||||
NextAuth.js Middleware 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="pages/admin/_middleware.ts"
|
||||
```ts title="middleware.ts"
|
||||
import type { NextRequest } from "next/server"
|
||||
import type { JWT } from "next-auth/jwt"
|
||||
|
||||
import { withAuth } from "next-auth/middleware"
|
||||
|
||||
export default withAuth(
|
||||
function middleware(req: NextRequest & { nextauth: { token: JWT } }) {
|
||||
// `withAuth` can augment your Request with the user's token.
|
||||
function middleware(req: NextRequest & { nextauth: { token: JWT | null } }) {
|
||||
console.log(req.nextauth.token)
|
||||
},
|
||||
{
|
||||
@@ -220,12 +175,53 @@ 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)
|
||||
|
||||
@@ -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,13 +161,19 @@ See repository [`README`](https://github.com/nextauthjs/react-query) for more de
|
||||
## getSession()
|
||||
|
||||
- Client Side: **Yes**
|
||||
- Server Side: **Yes**
|
||||
- Server Side: **No** (See: [`unstable_getServerSession()`](/configuration/nextjs#unstable_getserversession)
|
||||
|
||||
NextAuth.js provides a `getSession()` method which can be called client or server side to return a session.
|
||||
NextAuth.js provides a `getSession()` helper which should be called **client side only** to return the current active session.
|
||||
|
||||
It calls `/api/auth/session` and returns a promise with a session object, or null if no session exists.
|
||||
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).
|
||||
|
||||
#### Client Side Example
|
||||
:::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.
|
||||
|
||||
```js
|
||||
async function myFunction() {
|
||||
@@ -176,23 +182,7 @@ async function myFunction() {
|
||||
}
|
||||
```
|
||||
|
||||
#### 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.
|
||||
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()`.
|
||||
|
||||
---
|
||||
|
||||
@@ -254,7 +244,7 @@ export default async (req, res) => {
|
||||
```
|
||||
|
||||
:::note
|
||||
Unlike `getSession()` and `getCsrfToken()`, when calling `getProviders()` server side, you don't need to pass anything, just as calling it client side.
|
||||
Unlike and `getCsrfToken()`, when calling `getProviders()` server side, you don't need to pass anything, just as calling it client side.
|
||||
:::
|
||||
|
||||
---
|
||||
@@ -436,14 +426,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 { getSession } from "next-auth/react"
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
|
||||
...
|
||||
|
||||
export async function getServerSideProps(ctx) {
|
||||
return {
|
||||
props: {
|
||||
session: await getSession(ctx)
|
||||
session: await unstable_getServerSession(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -455,7 +445,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()` 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()`](/getting-started/client#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,13 +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 [`getSession()`](/getting-started/client#getsession) method in the NextAuth.js client.
|
||||
To protect an API Route, you can use the [`unstable_getServerSession()`](/configuration/nextjs#unstable_getserversession) method.
|
||||
|
||||
```javascript title="pages/api/restricted.js" showLineNumbers
|
||||
import { getSession } from "next-auth/react"
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
|
||||
export default async (req, res) => {
|
||||
const session = await getSession({ req })
|
||||
const session = await unstable_getServerSession(req, res, authOptions)
|
||||
|
||||
if (session) {
|
||||
res.send({
|
||||
@@ -143,7 +143,7 @@ callbacks: {
|
||||
...
|
||||
```
|
||||
|
||||
Now whenever you call `getSession` or `useSession`, the data object which is returned will include the `accessToken` value.
|
||||
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.
|
||||
|
||||
```jsx title="components/accessToken.jsx" showLineNumbers
|
||||
import { useSession, signIn, signOut } from "next-auth/react"
|
||||
|
||||
@@ -40,12 +40,28 @@ 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 `getSession()` method.
|
||||
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`.
|
||||
|
||||
```js title="pages/server-side-example.js"
|
||||
import { useSession, getSession } from "next-auth/react"
|
||||
import { useSession, unstable_getServerSession } from "next-auth/next"
|
||||
|
||||
export default function Page() {
|
||||
const { data: session } = useSession()
|
||||
@@ -66,7 +82,11 @@ export default function Page() {
|
||||
export async function getServerSideProps(context) {
|
||||
return {
|
||||
props: {
|
||||
session: await getSession(context),
|
||||
session: await unstable_getServerSession(
|
||||
context.req,
|
||||
context.res,
|
||||
authOptions
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -94,15 +114,15 @@ export default function App({
|
||||
|
||||
## Securing API Routes
|
||||
|
||||
### Using getSession()
|
||||
### Using unstable_getServerSession()
|
||||
|
||||
You can protect API routes using the `getSession()` method.
|
||||
You can protect API routes using the `unstable_getServerSession()` method.
|
||||
|
||||
```js title="pages/api/get-session-example.js"
|
||||
import { getSession } from "next-auth/react"
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
|
||||
export default async (req, res) => {
|
||||
const session = await getSession({ req })
|
||||
const session = await unstable_getServerSession(req, res, authOptions)
|
||||
if (session) {
|
||||
// Signed in
|
||||
console.log("Session", JSON.stringify(session, null, 2))
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
"generate-providers": "node ./scripts/generate-providers.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@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",
|
||||
"@docusaurus/core": "^2.0.0-beta.21",
|
||||
"@docusaurus/preset-classic": "^2.0.0-beta.21",
|
||||
"@docusaurus/remark-plugin-npm2yarn": "^2.0.0-beta.21",
|
||||
"@docusaurus/theme-common": "2.0.0-beta.21",
|
||||
"@mdx-js/react": "1.6.22",
|
||||
"classnames": "^2.3.1",
|
||||
"mdx-mermaid": "^1.2.2",
|
||||
|
||||
@@ -1,4 +1,23 @@
|
||||
{
|
||||
"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",
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"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,6 +166,10 @@ 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,7 +25,6 @@ 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.6.0",
|
||||
"version": "4.8.0",
|
||||
"description": "Authentication for Next.js",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth.git",
|
||||
@@ -62,7 +62,8 @@
|
||||
"index.js",
|
||||
"adapters.d.ts",
|
||||
"middleware.d.ts",
|
||||
"middleware.js"
|
||||
"middleware.js",
|
||||
"utils"
|
||||
],
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
@@ -102,6 +103,7 @@
|
||||
"@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",
|
||||
@@ -114,13 +116,13 @@
|
||||
"jest": "^28.1.1",
|
||||
"jest-environment-jsdom": "^28.1.1",
|
||||
"jest-watch-typeahead": "^1.1.0",
|
||||
"msw": "^0.42.1",
|
||||
"next": "^12.1.6",
|
||||
"msw": "^0.42.3",
|
||||
"next": "12.1.7-canary.51",
|
||||
"postcss": "^8.4.14",
|
||||
"postcss-cli": "^9.1.0",
|
||||
"postcss-nested": "^5.0.6",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"whatwg-fetch": "^3.6.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -136,4 +138,4 @@
|
||||
"**/tests",
|
||||
"**/__tests__"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,13 @@ 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?: Record<string, string>
|
||||
cookies?: Partial<Record<string, string>>
|
||||
headers?: Record<string, any>
|
||||
query?: Record<string, any>
|
||||
body?: Record<string, any>
|
||||
@@ -68,7 +69,7 @@ async function toInternalRequest(
|
||||
method: req.method,
|
||||
headers,
|
||||
body: await getBody(req),
|
||||
cookies: {},
|
||||
cookies: parseCookie(req.headers.get("cookie") ?? ""),
|
||||
providerId: nextauth[1],
|
||||
error: url.searchParams.get("error") ?? nextauth[1],
|
||||
host: detectHost(headers["x-forwarded-host"] ?? headers.host),
|
||||
|
||||
@@ -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 })
|
||||
await events.linkAccount?.({ user, account, profile })
|
||||
|
||||
// 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 })
|
||||
await events.linkAccount?.({ user, account, profile })
|
||||
|
||||
session = useJwtSession
|
||||
? {}
|
||||
|
||||
@@ -120,7 +120,7 @@ export class SessionStore {
|
||||
constructor(
|
||||
option: CookieOption,
|
||||
req: {
|
||||
cookies?: Record<string, string> | { get: (key: string) => string }
|
||||
cookies?: Partial<Record<string, string> | Map<string, string>>
|
||||
headers?: Headers | IncomingHttpHeaders | Record<string, string>
|
||||
},
|
||||
logger: LoggerInstance | Console
|
||||
@@ -128,14 +128,16 @@ export class SessionStore {
|
||||
this.#logger = logger
|
||||
this.#option = option
|
||||
|
||||
if (!req) return
|
||||
const { cookies } = req
|
||||
const { name: cookieName } = option
|
||||
|
||||
for (const name in req.cookies) {
|
||||
if (name.startsWith(option.name)) {
|
||||
this.#chunks[name] =
|
||||
typeof req.cookies.get === "function"
|
||||
? req.cookies.get(name)
|
||||
: req.cookies[name]
|
||||
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]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,12 +30,25 @@ 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() ?? null
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
let email = body?.email?.toLowerCase()
|
||||
|
||||
if (!email) return { redirect: `${url}/error?error=EmailSignin` }
|
||||
|
||||
email = email
|
||||
.split(",")[0]
|
||||
.trim()
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll('"', """)
|
||||
.replaceAll("'", "'")
|
||||
|
||||
// Verified in `assertConfig`
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
||||
@@ -14,7 +14,7 @@ import type { CookieSerializeOptions } from "cookie"
|
||||
|
||||
import type { NextApiRequest, NextApiResponse } from "next"
|
||||
|
||||
import { InternalUrl } from "../utils/parse-url"
|
||||
import type { InternalUrl } from "../utils/parse-url"
|
||||
|
||||
export type Awaitable<T> = T | PromiseLike<T>
|
||||
|
||||
@@ -389,7 +389,11 @@ 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 }) => Awaitable<void>
|
||||
linkAccount: (message: {
|
||||
user: User
|
||||
account: Account
|
||||
profile: User
|
||||
}) => Awaitable<void>
|
||||
/**
|
||||
* The message object will contain one of these depending on
|
||||
* if you use JWT or database persisted sessions:
|
||||
|
||||
@@ -96,6 +96,9 @@ export async function unstable_getServerSession(
|
||||
)
|
||||
|
||||
const [req, res, options] = args;
|
||||
|
||||
options.secret = options.secret ?? process.env.NEXTAUTH_SECRET
|
||||
|
||||
const session = await NextAuthHandler<Session | {}>({
|
||||
options,
|
||||
req: {
|
||||
|
||||
@@ -66,7 +66,7 @@ export interface NextAuthMiddlewareOptions {
|
||||
* @example
|
||||
*
|
||||
* ```js
|
||||
* // `pages/admin/_middleware.js`
|
||||
* // `middleware.js`
|
||||
* import { withAuth } from "next-auth/middleware"
|
||||
*
|
||||
* export default withAuth({
|
||||
@@ -74,6 +74,9 @@ export interface NextAuthMiddlewareOptions {
|
||||
* authorized: ({ token }) => token?.user.isAdmin
|
||||
* }
|
||||
* })
|
||||
*
|
||||
* export const config = { matcher: ["/admin"] }
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* ---
|
||||
@@ -149,7 +152,7 @@ export type WithAuthArgs =
|
||||
* @example
|
||||
*
|
||||
* ```js
|
||||
* // `pages/_middleware.js`
|
||||
* // `middleware.js`
|
||||
* export { default } from "next-auth/middleware"
|
||||
* ```
|
||||
*
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { InvalidCallbackUrl, MissingSecret } from "../src/core/errors"
|
||||
import { handler } from "./lib"
|
||||
|
||||
it("Show error page if secret is not defined", async () => {
|
||||
@@ -10,7 +11,7 @@ it("Show error page if secret is not defined", async () => {
|
||||
expect(res.html).toMatch(/there is a problem with the server configuration./i)
|
||||
expect(res.html).toMatch(/check the server logs for more information./i)
|
||||
|
||||
expect(log.error).toBeCalledWith("NO_SECRET", expect.anything())
|
||||
expect(log.error).toBeCalledWith("NO_SECRET", expect.any(MissingSecret))
|
||||
})
|
||||
|
||||
it("Should show configuration error page on invalid `callbackUrl`", async () => {
|
||||
@@ -25,7 +26,7 @@ it("Should show configuration error page on invalid `callbackUrl`", async () =>
|
||||
|
||||
expect(log.error).toBeCalledWith(
|
||||
"INVALID_CALLBACK_URL_ERROR",
|
||||
expect.anything()
|
||||
expect.any(InvalidCallbackUrl)
|
||||
)
|
||||
})
|
||||
|
||||
@@ -38,6 +39,6 @@ it("Allow relative `callbackUrl`", async () => {
|
||||
expect(res.status).not.toBe(500)
|
||||
expect(log.error).not.toBeCalledWith(
|
||||
"INVALID_CALLBACK_URL_ERROR",
|
||||
expect.anything()
|
||||
expect.any(InvalidCallbackUrl)
|
||||
)
|
||||
})
|
||||
|
||||
59
packages/next-auth/tests/email.test.ts
Normal file
59
packages/next-auth/tests/email.test.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { createCSRF, handler } from "./lib"
|
||||
import EmailProvider from "../src/providers/email"
|
||||
|
||||
const originalEmail = "balazs@email.com"
|
||||
|
||||
test.each([
|
||||
[originalEmail, `,<a href="example.com">Click here!</a>`],
|
||||
[originalEmail, ""],
|
||||
])("Sanitize email", async (emailOriginal, emailCompromised) => {
|
||||
const sendEmail = jest.fn()
|
||||
|
||||
const { secret, csrf } = createCSRF()
|
||||
|
||||
const email = {
|
||||
original: emailOriginal,
|
||||
compromised: `${emailOriginal}${emailCompromised}`,
|
||||
}
|
||||
|
||||
const { res } = await handler(
|
||||
{
|
||||
providers: [EmailProvider({ sendVerificationRequest: sendEmail })],
|
||||
adapter: {
|
||||
getUserByEmail: (email) => ({ id: "1", email, emailVerified: null }),
|
||||
createVerificationToken: (token) => token,
|
||||
} as any,
|
||||
secret,
|
||||
},
|
||||
{
|
||||
prod: true,
|
||||
path: "signin/email",
|
||||
requestInit: {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
email: email.compromised,
|
||||
csrfToken: csrf.value,
|
||||
}),
|
||||
headers: { "Content-Type": "application/json", Cookie: csrf.cookie },
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!emailCompromised) {
|
||||
expect(res.redirect).toBe(
|
||||
"http://localhost:3000/api/auth/verify-request?provider=email&type=email"
|
||||
)
|
||||
expect(sendEmail).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
identifier: email.original,
|
||||
token: expect.any(String),
|
||||
})
|
||||
)
|
||||
} else {
|
||||
expect(res.redirect).not.toContain("error=EmailSignin")
|
||||
|
||||
const emailTo = sendEmail.mock.calls[0][0].identifier
|
||||
expect(emailTo).not.toBe(email.compromised)
|
||||
expect(emailTo).toBe(email.original)
|
||||
}
|
||||
})
|
||||
53
packages/next-auth/tests/getServerSession.test.ts
Normal file
53
packages/next-auth/tests/getServerSession.test.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import type { NextApiRequest } from "next"
|
||||
import { MissingSecret } from "../src/core/errors"
|
||||
import { unstable_getServerSession } from "../src/next"
|
||||
import { mockLogger } from "./lib"
|
||||
|
||||
let originalWarn = console.warn
|
||||
let logger = mockLogger()
|
||||
|
||||
beforeEach(() => {
|
||||
process.env.NODE_ENV = "production"
|
||||
process.env.NEXTAUTH_URL = "http://localhost"
|
||||
console.warn = jest.fn()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
logger = mockLogger()
|
||||
process.env.NODE_ENV = "test"
|
||||
delete process.env.NEXTAUTH_URL
|
||||
console.warn = originalWarn
|
||||
})
|
||||
|
||||
describe("Treat secret correctly", () => {
|
||||
const req: any = { headers: {} }
|
||||
const res: any = { setHeader: jest.fn(), getHeader: jest.fn() }
|
||||
|
||||
it("Read from NEXTAUTH_SECRET", async () => {
|
||||
process.env.NEXTAUTH_SECRET = "secret"
|
||||
await unstable_getServerSession(req, res, { providers: [], logger })
|
||||
|
||||
expect(logger.error).toBeCalledTimes(0)
|
||||
expect(logger.error).not.toBeCalledWith("NO_SECRET")
|
||||
|
||||
delete process.env.NEXTAUTH_SECRET
|
||||
})
|
||||
|
||||
it("Read from options.secret", async () => {
|
||||
await unstable_getServerSession(req, res, {
|
||||
providers: [],
|
||||
logger,
|
||||
secret: "secret",
|
||||
})
|
||||
|
||||
expect(logger.error).toBeCalledTimes(0)
|
||||
expect(logger.error).not.toBeCalledWith("NO_SECRET")
|
||||
})
|
||||
|
||||
it("Error if missing NEXTAUTH_SECRET and secret", async () => {
|
||||
await unstable_getServerSession(req, res, { providers: [], logger })
|
||||
|
||||
expect(logger.error).toBeCalledTimes(1)
|
||||
expect(logger.error).toBeCalledWith("NO_SECRET", expect.any(MissingSecret))
|
||||
})
|
||||
})
|
||||
@@ -1,39 +1,37 @@
|
||||
import { createHash } from "crypto"
|
||||
import type { LoggerInstance, NextAuthOptions } from "../src"
|
||||
import { NextAuthHandler } from "../src/core"
|
||||
|
||||
export const mockLogger: () => LoggerInstance = () => ({
|
||||
error: jest.fn(() => {}),
|
||||
warn: jest.fn(() => {}),
|
||||
debug: jest.fn(() => {}),
|
||||
})
|
||||
|
||||
interface HandlerOptions {
|
||||
prod?: boolean
|
||||
path?: string
|
||||
params?: URLSearchParams | Record<string, string>
|
||||
requestInit?: RequestInit
|
||||
}
|
||||
|
||||
export async function handler(
|
||||
options: NextAuthOptions,
|
||||
{
|
||||
prod,
|
||||
path,
|
||||
params,
|
||||
}: {
|
||||
prod?: boolean
|
||||
path?: string
|
||||
params?: URLSearchParams | Record<string, string>
|
||||
}
|
||||
{ prod, path, params, requestInit }: HandlerOptions
|
||||
) {
|
||||
// @ts-ignore
|
||||
if (prod) process.env.NODE_ENV = "production"
|
||||
|
||||
const mockLogger: LoggerInstance = {
|
||||
error: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
}
|
||||
const url = new URL(
|
||||
`http://localhost/api/auth/${path ?? "signin"}?${new URLSearchParams(
|
||||
params ?? {}
|
||||
)}`
|
||||
)
|
||||
const req = new Request(url, {
|
||||
headers: {
|
||||
host: "",
|
||||
},
|
||||
})
|
||||
const req = new Request(url, { headers: { host: "" }, ...requestInit })
|
||||
const logger = mockLogger()
|
||||
const response = await NextAuthHandler({
|
||||
req,
|
||||
options: { secret: "secret", ...options, logger: mockLogger },
|
||||
options: { secret: "secret", ...options, logger },
|
||||
})
|
||||
// @ts-ignore
|
||||
if (prod) process.env.NODE_ENV = "test"
|
||||
@@ -44,6 +42,17 @@ export async function handler(
|
||||
html:
|
||||
response.headers?.[0].value === "text/html" ? response.body : undefined,
|
||||
},
|
||||
log: mockLogger,
|
||||
log: logger,
|
||||
}
|
||||
}
|
||||
|
||||
export function createCSRF() {
|
||||
const secret = "secret"
|
||||
const value = "csrf"
|
||||
const token = createHash("sha256").update(`${value}${secret}`).digest("hex")
|
||||
|
||||
return {
|
||||
secret,
|
||||
csrf: { value, token, cookie: `next-auth.csrf-token=${value}|${token}` },
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
"stripInternal": true,
|
||||
"skipDefaultLibCheck": true,
|
||||
"baseUrl": ".",
|
||||
"outDir": "."
|
||||
"outDir": ".",
|
||||
|
||||
},
|
||||
"exclude": ["./*.js", "./*.d.ts", "config", "**/__tests__", "tests"]
|
||||
}
|
||||
|
||||
12275
pnpm-lock.yaml
generated
12275
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ export async function verify() {
|
||||
if (!process.env.NPM_TOKEN_ORG) {
|
||||
throw new Error("NPM_TOKEN_ORG is not set")
|
||||
}
|
||||
if (!process.env.GITHUB_TOKEN) {
|
||||
throw new Error("GITHUB_TOKEN is not set")
|
||||
if (!process.env.RELEASE_TOKEN) {
|
||||
throw new Error("RELEASE_TOKEN is not set")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user