Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(next): allow unstable_getServerSession in Server Components #5741

Merged
merged 8 commits into from
Nov 6, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions apps/dev/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<head></head>
<body>{children}</body>
</html>
)
}
7 changes: 7 additions & 0 deletions apps/dev/app/server-component/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { unstable_getServerSession } from "next-auth/next"
import { authOptions } from "pages/api/auth/[...nextauth]"

export default async function Page() {
const session = await unstable_getServerSession(authOptions)
return <pre>{JSON.stringify(session, null, 2)}</pre>
}
1 change: 1 addition & 0 deletions apps/dev/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ module.exports = {
config.experiments = { ...config.experiments, topLevelAwait: true }
return config
},
experimental: { appDir: true },
typescript: { ignoreBuildErrors: true },
}
6 changes: 3 additions & 3 deletions apps/dev/pages/api/examples/session.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// This is an example of how to access a session from an API route
import { unstable_getServerSession } from "next-auth/next"
import { authOptions } from '../auth/[...nextauth]';
import { authOptions } from "../auth/[...nextauth]"

export default async (req, res) => {
const session = await unstable_getServerSession(req, res, authOptions)
res.send(JSON.stringify(session, null, 2))
const session = await unstable_getServerSession(authOptions)
res.json(session)
}
23 changes: 20 additions & 3 deletions apps/dev/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{
"compilerOptions": {
"target": "esnext",
"lib": ["dom", "dom.iterable", "esnext"],
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
Expand All @@ -15,7 +19,20 @@
"incremental": true,
"jsx": "preserve",
"baseUrl": ".",
"plugins": [
{
"name": "next"
}
]
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules", "jest.config.js"]
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules",
"jest.config.js"
]
}
7 changes: 3 additions & 4 deletions apps/example-nextjs/pages/server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,12 @@ export default function ServerSidePage({ session }: { session: Session }) {
<h1>Server Side Rendering</h1>
<p>
This page uses the <strong>unstable_getServerSession()</strong> method
in <strong>unstable_getServerSideProps()</strong>.
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.
<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
Expand Down
23 changes: 21 additions & 2 deletions docs/docs/configuration/nextjs.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const authOptions: NextAuthOptions = {
export default NextAuth(authOptions);
```

In `getServerSideProps`:
### In `getServerSideProps`:
```js
import { authOptions } from 'pages/api/auth/[...nextauth]'
import { unstable_getServerSession } from "next-auth/next"
Expand All @@ -48,7 +48,8 @@ export async function getServerSideProps(context) {
}
}
```
In API routes:

### In API Routes:
```js
import { authOptions } from 'pages/api/auth/[...nextauth]'
import { unstable_getServerSession } from "next-auth/next"
Expand All @@ -68,6 +69,24 @@ export async function handler(req, res) {
}
```

### In `app/` directory:

You can also use `unstable_getServerSession` in Next.js' server components:

```tsx
import { unstable_getServerSession } from "next-auth/next"
import { authOptions } from "pages/api/auth/[...nextauth]"

export default async function Page() {
const session = await unstable_getServerSession(authOptions)
return <pre>{JSON.stringify(session, null, 2)}</pre>
}
```

:::warning
Currently, the underlying Next.js `cookies()` method does [only provides read access](https://beta.nextjs.org/docs/api-reference/cookies) to the request cookies. This means that the `expires` value is stripped away from `session` in Server Components. Furthermore, there is a hard expiry on sessions, after which the user will be required to sign in again. (The default expiry is 30 days).
:::

## Middleware

You can use a Next.js Middleware with NextAuth.js to protect your site.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const authOptions: NextAuthOptions = {
export default NextAuth(authOptions);
```

In `getServerSideProps`:
### In `getServerSideProps`:
```js
import { authOptions } from 'pages/api/[...nextauth]'
import { unstable_getServerSession } from "next-auth/next"
Expand All @@ -48,7 +48,8 @@ export async function getServerSideProps(context) {
}
}
```
In API routes:

### In API routes:
```js
import { authOptions } from 'pages/api/[...nextauth]'
import { unstable_getServerSession } from "next-auth/next"
Expand All @@ -68,6 +69,24 @@ export async function handler(req, res) {
}
```

### In `app/` directory:

You can also use `unstable_getServerSession` in Next.js' server components:

```tsx
import { unstable_getServerSession } from "next-auth/next"
import { authOptions } from "pages/api/auth/[...nextauth]"

export default async function Page() {
const session = await unstable_getServerSession(authOptions)
return <pre>{JSON.stringify(session, null, 2)}</pre>
}
```

:::warning
Currently, the underlying Next.js `cookies()` method does [only provides read access](https://beta.nextjs.org/docs/api-reference/cookies) to the request cookies. This means that the `expires` value is stripped away from `session` in Server Components. Furthermore, there is a hard expiry on sessions, after which the user will be required to sign in again. (The default expiry is 30 days).
:::

## Middleware

You can use a Next.js Middleware with NextAuth.js to protect your site.
Expand Down
6 changes: 2 additions & 4 deletions packages/next-auth/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,8 @@ async function toInternalRequest(
const url = new URL(req.url)
// TODO: handle custom paths?
const nextauth = url.pathname.split("/").slice(3)
const headers = Object.fromEntries(req.headers.entries())
const query: Record<string, any> = Object.fromEntries(
url.searchParams.entries()
)
const headers = Object.fromEntries(req.headers)
const query: Record<string, any> = Object.fromEntries(url.searchParams)
query.nextauth = nextauth

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default async function getAuthorizationUrl({

if (typeof provider.authorization === "string") {
const parsedUrl = new URL(provider.authorization)
const parsedParams = Object.fromEntries(parsedUrl.searchParams.entries())
const parsedParams = Object.fromEntries(parsedUrl.searchParams)
params = { ...params, ...parsedParams }
} else {
params = { ...params, ...provider.authorization?.params }
Expand Down
43 changes: 41 additions & 2 deletions packages/next-auth/src/next/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NextAuthHandler } from "../core"
import { detectHost } from "../utils/detect-host"
import { setCookie } from "./utils"
import { cookies as nextCookies, headers } from "next/headers"

import type {
GetServerSidePropsContext,
Expand Down Expand Up @@ -84,6 +85,7 @@ function NextAuth(
export default NextAuth

let experimentalWarningShown = false
let experimentalRSCWarningShown = false
export async function unstable_getServerSession(
...args:
| [
Expand All @@ -92,6 +94,7 @@ export async function unstable_getServerSession(
NextAuthOptions
]
| [NextApiRequest, NextApiResponse, NextAuthOptions]
| [NextAuthOptions]
): Promise<Session | null> {
if (!experimentalWarningShown && process.env.NODE_ENV !== "production") {
console.warn(
Expand All @@ -103,7 +106,29 @@ export async function unstable_getServerSession(
experimentalWarningShown = true
}

const [req, res, options] = args
if (!experimentalRSCWarningShown && process.env.NODE_ENV !== "production") {
console.warn(
"[next-auth][warn][EXPERIMENTAL_API]",
"\n`unstable_getServerSession` is used in a React Server Component.",
`\nhttps://next-auth.js.org/configuration/nextjs#unstable_getServerSession}`,
`\nhttps://next-auth.js.org/warnings#EXPERIMENTAL_API`
)
experimentalRSCWarningShown = true
}

const isRSC = args.length === 1
const [req, res, options] = isRSC
? [
{
headers: ensureAvailable(headers),
cookies: ensureAvailable(nextCookies)
.getAll()
.reduce((acc, c) => ({ ...acc, [c.name]: c.value }), {}),
} as any,
{ getHeader() {}, setCookie() {}, setHeader() {} } as any,
args[0],
]
: args

options.secret = options.secret ?? process.env.NEXTAUTH_SECRET

Expand All @@ -123,7 +148,11 @@ export async function unstable_getServerSession(
cookies?.forEach((cookie) => setCookie(res, cookie))

if (body && typeof body !== "string" && Object.keys(body).length) {
if (status === 200) return body as Session
if (status === 200) {
// @ts-expect-error
delete body.expires
return body as Session
}
throw new Error((body as any).message)
}

Expand All @@ -139,3 +168,13 @@ declare global {
}
}
}

function ensureAvailable(fn: () => any) {
try {
return fn()
} catch (error) {
throw new Error(
`Could not access ${fn.name}(). If you are not in a React Server Cmponent, pass the request and response.`
)
}
}