mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
31 Commits
remove-uns
...
next-auth@
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50b117dfbb | ||
|
|
e6590ffc20 | ||
|
|
26c846594f | ||
|
|
2432ce9001 | ||
|
|
0a689b4f4e | ||
|
|
2fb34bab51 | ||
|
|
d0e7689d07 | ||
|
|
c004659174 | ||
|
|
c212e96f83 | ||
|
|
d41f2a4a02 | ||
|
|
5ecf20a804 | ||
|
|
9e423f3252 | ||
|
|
cf810f246a | ||
|
|
05fe398b1a | ||
|
|
8659c02366 | ||
|
|
2e039643b6 | ||
|
|
3943f9b7b2 | ||
|
|
f2e85c2113 | ||
|
|
c53c868288 | ||
|
|
0bc4fcb51a | ||
|
|
139c2edb50 | ||
|
|
4e94d89554 | ||
|
|
43d66fcb23 | ||
|
|
bfcf1a3604 | ||
|
|
5b1555ed97 | ||
|
|
0ed07b31b6 | ||
|
|
2311be7589 | ||
|
|
e847b3466f | ||
|
|
8df6d5b469 | ||
|
|
0bcaeca369 | ||
|
|
4f5ddbcb76 |
@@ -1,6 +1,6 @@
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { getServerSession } from "next-auth/next"
|
||||
|
||||
export default async function Page() {
|
||||
const session = await unstable_getServerSession()
|
||||
const session = await getServerSession()
|
||||
return <pre>{JSON.stringify(session, null, 2)}</pre>
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// This is an example of to protect an API route
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "../auth/[...nextauth]"
|
||||
|
||||
export default async (req, res) => {
|
||||
const session = await unstable_getServerSession(req, res, authOptions)
|
||||
const session = await getServerSession(req, res, authOptions)
|
||||
|
||||
if (session) {
|
||||
res.send({
|
||||
|
||||
@@ -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 { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "../auth/[...nextauth]"
|
||||
|
||||
export default async (req, res) => {
|
||||
const session = await unstable_getServerSession(req, res, authOptions)
|
||||
const session = await getServerSession(req, res, authOptions)
|
||||
res.json(session)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// This is an example of how to query data from Supabase with RLS.
|
||||
// Learn more about Row Levele Security (RLS): https://supabase.com/docs/guides/auth/row-level-security
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "../auth/[...nextauth]"
|
||||
import { createClient } from "@supabase/supabase-js"
|
||||
|
||||
export default async (req, res) => {
|
||||
const session = await unstable_getServerSession(req, res, authOptions)
|
||||
const session = await getServerSession(req, res, authOptions)
|
||||
|
||||
if (!session)
|
||||
return res.send(JSON.stringify({ error: "No session!" }, null, 2))
|
||||
|
||||
@@ -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.req, context.res, authOptions)
|
||||
let content = null
|
||||
|
||||
if (session) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import Layout from "../components/layout"
|
||||
import { authOptions } from './api/auth/[...nextauth]';
|
||||
import { authOptions } from "./api/auth/[...nextauth]"
|
||||
|
||||
export default function Page() {
|
||||
// As this page uses Server Side Rendering, the `session` will be already
|
||||
@@ -12,11 +12,11 @@ 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 <strong>getServerSession()</strong> method in{" "}
|
||||
<strong>getServerSideProps()</strong>.
|
||||
</p>
|
||||
<p>
|
||||
Using <strong>unstable_getServerSession()</strong> in{" "}
|
||||
Using <strong>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.
|
||||
@@ -40,11 +40,7 @@ export default function Page() {
|
||||
export async function getServerSideProps(context) {
|
||||
return {
|
||||
props: {
|
||||
session: await unstable_getServerSession(
|
||||
context.req,
|
||||
context.res,
|
||||
authOptions
|
||||
),
|
||||
session: await getServerSession(context.req, context.res, authOptions),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// This is an example of how to protect content using server rendering
|
||||
// and fetching data from Supabase with RLS enabled.
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "./api/auth/[...nextauth]"
|
||||
import { createClient } from "@supabase/supabase-js"
|
||||
import Layout from "../components/layout"
|
||||
@@ -27,11 +27,7 @@ export default function Page({ data, session }) {
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const session = await unstable_getServerSession(
|
||||
context.req,
|
||||
context.res,
|
||||
authOptions
|
||||
)
|
||||
const session = await getServerSession(context.req, context.res, authOptions)
|
||||
|
||||
if (!session)
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// This is an example of to protect an API route
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "../auth/[...nextauth]"
|
||||
|
||||
import type { NextApiRequest, NextApiResponse } from "next"
|
||||
@@ -8,7 +8,7 @@ export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const session = await unstable_getServerSession(req, res, authOptions)
|
||||
const session = await getServerSession(req, res, authOptions)
|
||||
|
||||
if (session) {
|
||||
return res.send({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// This is an example of how to access a session from an API route
|
||||
import { unstable_getServerSession } from "next-auth"
|
||||
import { getServerSession } from "next-auth"
|
||||
import { authOptions } from "../auth/[...nextauth]"
|
||||
|
||||
import type { NextApiRequest, NextApiResponse } from "next"
|
||||
@@ -8,6 +8,6 @@ export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const session = await unstable_getServerSession(req, res, authOptions)
|
||||
const session = await getServerSession(req, res, authOptions)
|
||||
res.send(JSON.stringify(session, null, 2))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "./api/auth/[...nextauth]"
|
||||
import Layout from "../components/layout"
|
||||
|
||||
@@ -12,11 +12,11 @@ export default function ServerSidePage({ session }: { session: Session }) {
|
||||
<Layout>
|
||||
<h1>Server Side Rendering</h1>
|
||||
<p>
|
||||
This page uses the <strong>unstable_getServerSession()</strong> method
|
||||
in <strong>getServerSideProps()</strong>.
|
||||
This page uses the <strong>getServerSession()</strong> method in{" "}
|
||||
<strong>getServerSideProps()</strong>.
|
||||
</p>
|
||||
<p>
|
||||
Using <strong>unstable_getServerSession()</strong> in{" "}
|
||||
Using <strong>getServerSession()</strong> in{" "}
|
||||
<strong>getServerSideProps()</strong> is the recommended approach if you
|
||||
need to support Server Side Rendering with authentication.
|
||||
</p>
|
||||
@@ -37,11 +37,7 @@ export default function ServerSidePage({ session }: { session: Session }) {
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
return {
|
||||
props: {
|
||||
session: await unstable_getServerSession(
|
||||
context.req,
|
||||
context.res,
|
||||
authOptions
|
||||
),
|
||||
session: await getServerSession(context.req, context.res, authOptions),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,7 +254,7 @@ The `supabaseAccessToken` is now available on the `session` object and can be pa
|
||||
|
||||
```js
|
||||
// ...
|
||||
// Use `useSession()` or `unstable_getServerSession()` to get the NextAuth session.
|
||||
// Use `useSession()` or `getServerSession()` to get the NextAuth session.
|
||||
|
||||
const { supabaseAccessToken } = session
|
||||
|
||||
|
||||
@@ -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()`, `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.
|
||||
@@ -139,8 +139,8 @@ The session callback is called whenever a session is checked. By default, **only
|
||||
|
||||
e.g. `getSession()`, `useSession()`, `/api/auth/session`
|
||||
|
||||
- When using database sessions, the User object is passed as an argument.
|
||||
- When using JSON Web Tokens for sessions, the JWT payload is provided instead.
|
||||
- When using database sessions, the User (`user`) object is passed as an argument.
|
||||
- When using JSON Web Tokens for sessions, the JWT payload (`token`) is provided instead.
|
||||
|
||||
```js title="pages/api/auth/[...nextauth].js"
|
||||
...
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
## `unstable_getServerSession`
|
||||
|
||||
:::warning
|
||||
This feature is experimental and may be removed or changed in the future.
|
||||
:::
|
||||
This method was renamed to `getServerSession`. See the documentation below.
|
||||
|
||||
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.
|
||||
## `getServerSession`
|
||||
|
||||
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` on 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, `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:
|
||||
`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].ts`:
|
||||
```ts
|
||||
@@ -27,10 +27,10 @@ export default NextAuth(authOptions);
|
||||
### In `getServerSideProps`:
|
||||
```js
|
||||
import { authOptions } from 'pages/api/auth/[...nextauth]'
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { getServerSession } from "next-auth/next"
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const session = await unstable_getServerSession(context.req, context.res, authOptions)
|
||||
const session = await getServerSession(context.req, context.res, authOptions)
|
||||
|
||||
if (!session) {
|
||||
return {
|
||||
@@ -52,11 +52,11 @@ export async function getServerSideProps(context) {
|
||||
### In API Routes:
|
||||
```js
|
||||
import { authOptions } from 'pages/api/auth/[...nextauth]'
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { getServerSession } from "next-auth/next"
|
||||
|
||||
|
||||
export async function handler(req, res) {
|
||||
const session = await unstable_getServerSession(req, res, authOptions)
|
||||
const session = await getServerSession(req, res, authOptions)
|
||||
|
||||
if (!session) {
|
||||
res.status(401).json({ message: "You must be logged in." });
|
||||
@@ -71,20 +71,20 @@ export async function handler(req, res) {
|
||||
|
||||
### In `app/` directory:
|
||||
|
||||
You can also use `unstable_getServerSession` in Next.js' server components:
|
||||
You can also use `getServerSession` in Next.js' server components:
|
||||
|
||||
```tsx
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "pages/api/auth/[...nextauth]"
|
||||
|
||||
export default async function Page() {
|
||||
const session = await unstable_getServerSession(authOptions)
|
||||
const session = await 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).
|
||||
Currently, the underlying Next.js `cookies()` method [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
|
||||
|
||||
@@ -77,10 +77,13 @@ In addition, you can define a `theme.brandColor` to define a custom accent color
|
||||
|
||||
In order to get the available authentication providers and the URLs to use for them, you can make a request to the API endpoint `/api/auth/providers`:
|
||||
|
||||
```jsx title="pages/auth/signin.js"
|
||||
```tsx title="pages/auth/signin.tsx"
|
||||
import type { GetServerSidePropsContext, InferGetServerSidePropsType } from "next";
|
||||
import { getProviders, signIn } from "next-auth/react"
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "../api/auth/[...nextauth]";
|
||||
|
||||
export default function SignIn({ providers }) {
|
||||
export default function SignIn({ providers }: InferGetServerSidePropsType<typeof getServerSideProps>) {
|
||||
return (
|
||||
<>
|
||||
{Object.values(providers).map((provider) => (
|
||||
@@ -94,10 +97,20 @@ export default function SignIn({ providers }) {
|
||||
)
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const providers = await getProviders()
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
const session = await getServerSession(context.req, context.res, authOptions);
|
||||
|
||||
// If the user is already logged in, redirect.
|
||||
// Note: Make sure not to redirect to the same page
|
||||
// To avoid an infinite loop!
|
||||
if (session) {
|
||||
return { redirect: { destination: "/" } };
|
||||
}
|
||||
|
||||
const providers = await getProviders();
|
||||
|
||||
return {
|
||||
props: { providers },
|
||||
props: { providers: providers ?? [] },
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -108,10 +121,11 @@ There is another, more fully styled example signin page available [here](https:/
|
||||
|
||||
If you create a custom sign in form for email sign in, you will need to submit both fields for the **email** address and **csrfToken** from **/api/auth/csrf** in a POST request to **/api/auth/signin/email**.
|
||||
|
||||
```jsx title="pages/auth/email-signin.js"
|
||||
```tsx title="pages/auth/email-signin.tsx"
|
||||
import type { GetServerSidePropsContext, InferGetServerSidePropsType } from "next";
|
||||
import { getCsrfToken } from "next-auth/react"
|
||||
|
||||
export default function SignIn({ csrfToken }) {
|
||||
export default function SignIn({ csrfToken }: InferGetServerSidePropsType<typeof getServerSideProps>) {
|
||||
return (
|
||||
<form method="post" action="/api/auth/signin/email">
|
||||
<input name="csrfToken" type="hidden" defaultValue={csrfToken} />
|
||||
@@ -124,7 +138,7 @@ export default function SignIn({ csrfToken }) {
|
||||
)
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
const csrfToken = await getCsrfToken(context)
|
||||
return {
|
||||
props: { csrfToken },
|
||||
@@ -134,7 +148,7 @@ export async function getServerSideProps(context) {
|
||||
|
||||
You can also use the `signIn()` function which will handle obtaining the CSRF token for you:
|
||||
|
||||
```js
|
||||
```ts
|
||||
signIn("email", { email: "jsmith@example.com" })
|
||||
```
|
||||
|
||||
@@ -142,10 +156,11 @@ signIn("email", { email: "jsmith@example.com" })
|
||||
|
||||
If you create a sign in form for credentials based authentication, you will need to pass a **csrfToken** from **/api/auth/csrf** in a POST request to **/api/auth/callback/credentials**.
|
||||
|
||||
```jsx title="pages/auth/credentials-signin.js"
|
||||
```tsx title="pages/auth/credentials-signin.tsx"
|
||||
import type { GetServerSidePropsContext, InferGetServerSidePropsType } from "next";
|
||||
import { getCsrfToken } from "next-auth/react"
|
||||
|
||||
export default function SignIn({ csrfToken }) {
|
||||
export default function SignIn({ csrfToken }: InferGetServerSidePropsType<typeof getServerSideProps>) {
|
||||
return (
|
||||
<form method="post" action="/api/auth/callback/credentials">
|
||||
<input name="csrfToken" type="hidden" defaultValue={csrfToken} />
|
||||
@@ -162,7 +177,7 @@ export default function SignIn({ csrfToken }) {
|
||||
)
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
export async function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
return {
|
||||
props: {
|
||||
csrfToken: await getCsrfToken(context),
|
||||
@@ -173,7 +188,7 @@ export async function getServerSideProps(context) {
|
||||
|
||||
You can also use the `signIn()` function which will handle obtaining the CSRF token for you:
|
||||
|
||||
```js
|
||||
```ts
|
||||
signIn("credentials", { username: "jsmith", password: "1234" })
|
||||
```
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ sequenceDiagram
|
||||
Note left of Browser: User inserts their<br/>credentials in Github
|
||||
Browser->>Auth Server (Github): Github validates the inserted credentials
|
||||
Auth Server (Github)->>Auth Server (Github): Generates one time access code<br/>and calls callback<br>URL defined in<br/>App settings
|
||||
Auth Server (Github)->>App Server: GET<br/>"api/auth/github/callback?code=123"
|
||||
Auth Server (Github)->>App Server: GET<br/>"api/auth/callback/github?code=123"
|
||||
App Server->>App Server: Grabs code<br/>to exchange it for<br/>access token
|
||||
App Server->>Auth Server (Github): POST<br/>"github.com/login/oauth/access_token"<br/>{code: 123}
|
||||
Auth Server (Github)->>Auth Server (Github): Verifies code is<br/>valid and generates<br/>access token
|
||||
@@ -424,17 +424,3 @@ GoogleProvider({
|
||||
allowDangerousEmailAccountLinking: true,
|
||||
})
|
||||
```
|
||||
|
||||
### Adding a new built-in provider
|
||||
|
||||
If you think your custom provider might be useful to others, we encourage you to open a PR and add it to the built-in list so others can discover it much more easily!
|
||||
|
||||
You only need to add three changes:
|
||||
|
||||
1. Add your config: [`src/providers/{provider}.ts`](https://github.com/nextauthjs/next-auth/tree/main/packages/next-auth/src/providers)<br />
|
||||
- Make sure you use a named default export, like this: `export default function YourProvider`
|
||||
- Add two SVG's of the provider logo, like `google-dark.svg` (dark mode) and `google.svg` (light mode), to the `/packages/next-auth/provider-logos/` directory as well as the styling config to the provider config object. See existing provider for example
|
||||
2. Add provider documentation: [`/docs/providers/{provider}.md`](https://github.com/nextauthjs/next-auth/tree/main/docs/docs/providers)
|
||||
3. Add the new provider name to the `Provider type` dropdown options in [`the provider issue template`](https://github.com/nextauthjs/next-auth/edit/main/.github/ISSUE_TEMPLATE/2_bug_provider.yml)
|
||||
|
||||
That's it! 🎉 Others will be able to discover and use this provider much more easily now!
|
||||
|
||||
@@ -79,7 +79,7 @@ export default NextAuth({
|
||||
|
||||
#### Using the branch based preview URL
|
||||
|
||||
Preview deployments at Vercel are often available via multiple URLs. For example, PR's merged to `master` or `main`, will be available the commit and PR specific preview URLs, but also the branch specific preview URLs. This branch specific URL will obviously not change as long as you work with that same branch. Therefore, you could add to your OAuth provider your `{project}-git-main-{user}.vercel.app` preview URL. As this will stay constant for that branch, you can reuse that preview deployment / URL for testing any authentication related deployments.
|
||||
Preview deployments at Vercel are often available via multiple URLs. For example, PR's merged to `master` or `main`, will be available via commit and PR specific preview URLs, but also the branch specific preview URLs. This branch specific URL will obviously not change as long as you work with that same branch. Therefore, you could add to your OAuth provider your `{project}-git-main-{user}.vercel.app` preview URL. As this will stay constant for that branch, you can reuse that preview deployment / URL for testing any authentication related deployments.
|
||||
|
||||
## Netlify
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ _If you use a custom credentials provider user accounts will not be persisted in
|
||||
</summary>
|
||||
<p>
|
||||
|
||||
NextAuth.js was originally designed for use with Next.js and Serverless. However, today you could use the NextAuth.js core with any other framework. Checkout the examples for <a href="https://github.com/nextauthjs/next-auth/tree/main/apps/playground-gatsby" target="_blank">Gatsby</a> and <a href="https://sveltekit.authjs.dev/" target="_blank">SvelteKit</a>. If you would add another integration with other frameworks, feel free to work on it and send a pull request. Make sure to check if there's any on-going work before open a new issue.
|
||||
NextAuth.js was originally designed for use with Next.js and Serverless. However, today you could use the NextAuth.js core with any other framework. Checkout the examples for <a href="https://github.com/nextauthjs/next-auth/tree/main/apps/playground-gatsby" target="_blank">Gatsby</a> and <a href="https://sveltekit.authjs.dev/" target="_blank">SvelteKit</a>. If you would add another integration with other frameworks, feel free to work on it and send a pull request. Make sure to check if there's any on-going work before opening a new issue.
|
||||
|
||||
</p>
|
||||
</details>
|
||||
|
||||
@@ -157,15 +157,11 @@ You can create your own session management solution using data fetching librarie
|
||||
## getSession()
|
||||
|
||||
- Client Side: **Yes**
|
||||
- Server Side: **No** (See: [`unstable_getServerSession()`](/configuration/nextjs#unstable_getserversession)
|
||||
- Server Side: **No** (See: [`getServerSession()`](/configuration/nextjs#unstable_getserversession)
|
||||
|
||||
NextAuth.js provides a `getSession()` helper which should be called **client side only** to return the current active 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).
|
||||
|
||||
:::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.
|
||||
:::
|
||||
On the server side, **this is still available to use**, however, we recommend using `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).
|
||||
|
||||
This helper is helpful in case you want to read the session outside of the context of React.
|
||||
|
||||
@@ -178,7 +174,7 @@ 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()`.
|
||||
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 `getServerSession()`.
|
||||
|
||||
---
|
||||
|
||||
@@ -422,7 +418,7 @@ 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 { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from './api/auth/[...nextauth]'
|
||||
|
||||
...
|
||||
@@ -430,7 +426,7 @@ import { authOptions } from './api/auth/[...nextauth]'
|
||||
export async function getServerSideProps({ req, res }) {
|
||||
return {
|
||||
props: {
|
||||
session: await unstable_getServerSession(req, res, authOptions)
|
||||
session: await getServerSession(req, res, authOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,14 +106,14 @@ 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 [`getServerSession()`](/configuration/nextjs#unstable_getserversession) method.
|
||||
|
||||
```javascript title="pages/api/restricted.js" showLineNumbers
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "./auth/[...nextauth]"
|
||||
|
||||
export default async (req, res) => {
|
||||
const session = await unstable_getServerSession(req, res, authOptions)
|
||||
const session = await getServerSession(req, res, authOptions)
|
||||
|
||||
if (session) {
|
||||
res.send({
|
||||
|
||||
@@ -64,7 +64,7 @@ Edit your host file and point your site to `127.0.0.1`.
|
||||
_Linux/macOS_
|
||||
|
||||
```
|
||||
sudo echo '127.0.0.1 dev.example.com' >> /etc/hosts
|
||||
echo '127.0.0.1 dev.example.com' | sudo tee -a /etc/hosts
|
||||
```
|
||||
|
||||
_Windows_ (run PowerShell as administrator)
|
||||
|
||||
53
docs/docs/providers/duende-identity-server6.md
Normal file
53
docs/docs/providers/duende-identity-server6.md
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
id: duende-identityserver6
|
||||
title: DuendeIdentityServer6
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
https://docs.duendesoftware.com/identityserver/v6
|
||||
|
||||
## Options
|
||||
|
||||
The **DuendeIdentityServer6 Provider** comes with a set of default options:
|
||||
|
||||
- [DuendeIdentityServer6 Provider options](https://github.com/nextauthjs/next-auth/blob/v4/packages/next-auth/src/providers/duende-identity-server6.ts)
|
||||
|
||||
You can override any of the options to suit your own use case.
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
import DuendeIDS6Provider from "next-auth/providers/duende-identity-server6"
|
||||
|
||||
...
|
||||
providers: [
|
||||
DuendeIDS6Provider({
|
||||
clientId: process.env.DUENDE_IDS6_ID,
|
||||
clientSecret: process.env.DUENDE_IDS6_SECRET,
|
||||
issuer: process.env.DUENDE_IDS6_ISSUER,
|
||||
})
|
||||
]
|
||||
...
|
||||
```
|
||||
|
||||
## Demo IdentityServer
|
||||
|
||||
The configuration below is for the demo server at https://demo.duendesoftware.com/
|
||||
|
||||
If you want to try it out, you can copy and paste the configuration below.
|
||||
|
||||
You can sign in to the demo service with either <b>bob/bob</b> or <b>alice/alice</b>.
|
||||
|
||||
```js
|
||||
import DuendeIDS6Provider from "next-auth/providers/duende-identity-server6"
|
||||
...
|
||||
providers: [
|
||||
DuendeIDS6Provider({
|
||||
clientId: "interactive.confidential",
|
||||
clientSecret: "secret",
|
||||
issuer: "https://demo.duendesoftware.com",
|
||||
})
|
||||
]
|
||||
...
|
||||
```
|
||||
@@ -68,12 +68,12 @@ For other patterns check out the [Next.js Middleware documentation](https://next
|
||||
|
||||
### 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 can protect server side rendered pages using the `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 need to add this to every server rendered page you want to protect. Be aware, `getServerSession` takes slightly different arguments than the method it is replacing, `getSession`.
|
||||
|
||||
```js title="pages/server-side-example.js"
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "./api/auth/[...nextauth]"
|
||||
import { useSession } from "next-auth/react"
|
||||
|
||||
@@ -96,7 +96,7 @@ export default function Page() {
|
||||
export async function getServerSideProps(context) {
|
||||
return {
|
||||
props: {
|
||||
session: await unstable_getServerSession(
|
||||
session: await getServerSession(
|
||||
context.req,
|
||||
context.res,
|
||||
authOptions
|
||||
@@ -128,16 +128,16 @@ export default function App({
|
||||
|
||||
## Securing API Routes
|
||||
|
||||
### Using unstable_getServerSession()
|
||||
### Using getServerSession()
|
||||
|
||||
You can protect API routes using the `unstable_getServerSession()` method.
|
||||
You can protect API routes using the `getServerSession()` method.
|
||||
|
||||
```js title="pages/api/get-session-example.js"
|
||||
import { unstable_getServerSession } from "next-auth/next"
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "./auth/[...nextauth]"
|
||||
|
||||
export default async (req, res) => {
|
||||
const session = await unstable_getServerSession(req, res, authOptions)
|
||||
const session = await getServerSession(req, res, authOptions)
|
||||
if (session) {
|
||||
// Signed in
|
||||
console.log("Session", JSON.stringify(session, null, 2))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "https://openapi.vercel.sh/vercel.json",
|
||||
"headers": [
|
||||
{
|
||||
"source": "/(.*)",
|
||||
@@ -57,6 +58,21 @@
|
||||
"source": "/schemas/adapters",
|
||||
"destination": "/adapters/overview",
|
||||
"permanent": true
|
||||
},
|
||||
{
|
||||
"source": "/tutorials/role-based-login-strategy",
|
||||
"destination": "https://authjs.dev/guides/basics/role-based-authentication",
|
||||
"permanent": true
|
||||
},
|
||||
{
|
||||
"source": "/adapters/firebase",
|
||||
"destination": "https://authjs.dev/reference/adapter/firebase",
|
||||
"permanent": true
|
||||
},
|
||||
{
|
||||
"source": "/tutorials/refresh-token-rotation",
|
||||
"destination": "https://authjs.dev/guides/basics/refresh-token-rotation",
|
||||
"permanent": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "next-auth",
|
||||
"version": "4.18.10",
|
||||
"version": "4.20.1",
|
||||
"description": "Authentication for Next.js",
|
||||
"homepage": "https://next-auth.js.org",
|
||||
"repository": "https://github.com/nextauthjs/next-auth.git",
|
||||
@@ -67,12 +67,12 @@
|
||||
],
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.16.3",
|
||||
"@panva/hkdf": "^1.0.1",
|
||||
"@babel/runtime": "^7.20.13",
|
||||
"@panva/hkdf": "^1.0.2",
|
||||
"cookie": "^0.5.0",
|
||||
"jose": "^4.9.3",
|
||||
"jose": "^4.11.4",
|
||||
"oauth": "^0.9.15",
|
||||
"openid-client": "^5.1.0",
|
||||
"openid-client": "^5.4.0",
|
||||
"preact": "^10.6.3",
|
||||
"preact-render-to-string": "^5.1.19",
|
||||
"uuid": "^8.3.2"
|
||||
|
||||
@@ -61,7 +61,7 @@ export function assertConfig(params: {
|
||||
return new MissingSecret("Please define a `secret` in production.")
|
||||
}
|
||||
|
||||
// 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."
|
||||
|
||||
@@ -113,7 +113,7 @@ export default async function callbackHandler(params: {
|
||||
session = useJwtSession
|
||||
? {}
|
||||
: await createSession({
|
||||
sessionToken: generateSessionToken(),
|
||||
sessionToken: await generateSessionToken(),
|
||||
userId: user.id,
|
||||
expires: fromDate(options.session.maxAge),
|
||||
})
|
||||
@@ -143,7 +143,7 @@ export default async function callbackHandler(params: {
|
||||
session = useJwtSession
|
||||
? {}
|
||||
: await createSession({
|
||||
sessionToken: generateSessionToken(),
|
||||
sessionToken: await generateSessionToken(),
|
||||
userId: userByAccount.id,
|
||||
expires: fromDate(options.session.maxAge),
|
||||
})
|
||||
@@ -181,11 +181,11 @@ export default async function callbackHandler(params: {
|
||||
? await getUserByEmail(profile.email)
|
||||
: null
|
||||
if (userByEmail) {
|
||||
const provider = options.provider as OAuthConfig<any>;
|
||||
const provider = options.provider as OAuthConfig<any>
|
||||
if (provider?.allowDangerousEmailAccountLinking) {
|
||||
// If you trust the oauth provider to correctly verify email addresses, you can opt-in to
|
||||
// If you trust the oauth provider to correctly verify email addresses, you can opt-in to
|
||||
// account linking even when the user is not signed-in.
|
||||
user = userByEmail;
|
||||
user = userByEmail
|
||||
} else {
|
||||
// We end up here when we don't have an account with the same [provider].id *BUT*
|
||||
// we do already have an account with the same email address as the one in the
|
||||
@@ -216,7 +216,7 @@ export default async function callbackHandler(params: {
|
||||
session = useJwtSession
|
||||
? {}
|
||||
: await createSession({
|
||||
sessionToken: generateSessionToken(),
|
||||
sessionToken: await generateSessionToken(),
|
||||
userId: user.id,
|
||||
expires: fromDate(options.session.maxAge),
|
||||
})
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { openidClient } from "./client"
|
||||
import { oAuth1Client } from "./client-legacy"
|
||||
import { createState } from "./state-handler"
|
||||
import { createNonce } from "./nonce-handler"
|
||||
import { createPKCE } from "./pkce-handler"
|
||||
import { oAuth1Client, oAuth1TokenStore } from "./client-legacy"
|
||||
import * as checks from "./checks"
|
||||
|
||||
import type { AuthorizationParameters } from "openid-client"
|
||||
import type { InternalOptions } from "../../types"
|
||||
@@ -44,7 +42,7 @@ export default async function getAuthorizationUrl({
|
||||
oauth_token_secret: tokens.oauth_token_secret,
|
||||
...tokens.params,
|
||||
})}`
|
||||
|
||||
oAuth1TokenStore.set(tokens.oauth_token, tokens.oauth_token_secret)
|
||||
logger.debug("GET_AUTHORIZATION_URL", { url, provider })
|
||||
return { redirect: url }
|
||||
}
|
||||
@@ -54,24 +52,9 @@ export default async function getAuthorizationUrl({
|
||||
const authorizationParams: AuthorizationParameters = params
|
||||
const cookies: Cookie[] = []
|
||||
|
||||
const state = await createState(options)
|
||||
if (state) {
|
||||
authorizationParams.state = state.value
|
||||
cookies.push(state.cookie)
|
||||
}
|
||||
|
||||
const nonce = await createNonce(options)
|
||||
if (nonce) {
|
||||
authorizationParams.nonce = nonce.value
|
||||
cookies.push(nonce.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)
|
||||
}
|
||||
await checks.state.create(options, cookies, authorizationParams)
|
||||
await checks.pkce.create(options, cookies, authorizationParams)
|
||||
await checks.nonce.create(options, cookies, authorizationParams)
|
||||
|
||||
const url = client.authorizationUrl(authorizationParams)
|
||||
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
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 { useNonce } from "./nonce-handler"
|
||||
import { oAuth1Client, oAuth1TokenStore } from "./client-legacy"
|
||||
import * as _checks from "./checks"
|
||||
import { OAuthCallbackError } from "../../errors"
|
||||
|
||||
import type { CallbackParamsType, OpenIDCallbackChecks } from "openid-client"
|
||||
import type { CallbackParamsType } from "openid-client"
|
||||
import type { LoggerInstance, Profile } from "../../.."
|
||||
import type { OAuthChecks, OAuthConfig } from "../../../providers"
|
||||
import type { InternalOptions } from "../../types"
|
||||
@@ -42,7 +40,7 @@ export default async function oAuthCallback(params: {
|
||||
const { oauth_token, oauth_verifier } = query ?? {}
|
||||
const tokens = (await (client as any).getOAuthAccessToken(
|
||||
oauth_token,
|
||||
null,
|
||||
oAuth1TokenStore.get(oauth_token),
|
||||
oauth_verifier
|
||||
)) as TokenSet
|
||||
let profile: Profile = await (client as any).get(
|
||||
@@ -63,6 +61,8 @@ export default async function oAuthCallback(params: {
|
||||
}
|
||||
}
|
||||
|
||||
if (query?.oauth_token) oAuth1TokenStore.delete(query.oauth_token)
|
||||
|
||||
try {
|
||||
const client = await openidClient(options)
|
||||
|
||||
@@ -71,24 +71,9 @@ export default async function oAuthCallback(params: {
|
||||
const checks: OAuthChecks = {}
|
||||
const resCookies: Cookie[] = []
|
||||
|
||||
const state = await useState(cookies?.[options.cookies.state.name], options)
|
||||
if (state) {
|
||||
checks.state = state.value
|
||||
resCookies.push(state.cookie)
|
||||
}
|
||||
|
||||
const nonce = await useNonce(cookies?.[options.cookies.nonce.name], options)
|
||||
if (nonce && provider.idToken) {
|
||||
;(checks as OpenIDCallbackChecks).nonce = nonce.value
|
||||
resCookies.push(nonce.cookie)
|
||||
}
|
||||
|
||||
const codeVerifier = cookies?.[options.cookies.pkceCodeVerifier.name]
|
||||
const pkce = await usePKCECodeVerifier(codeVerifier, options)
|
||||
if (pkce) {
|
||||
checks.code_verifier = pkce.codeVerifier
|
||||
resCookies.push(pkce.cookie)
|
||||
}
|
||||
await _checks.state.use(cookies, resCookies, options, checks)
|
||||
await _checks.pkce.use(cookies, resCookies, options, checks)
|
||||
await _checks.nonce.use(cookies, resCookies, options, checks)
|
||||
|
||||
const params: CallbackParamsType = {
|
||||
...client.callbackParams({
|
||||
|
||||
181
packages/next-auth/src/core/lib/oauth/checks.ts
Normal file
181
packages/next-auth/src/core/lib/oauth/checks.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import {
|
||||
AuthorizationParameters,
|
||||
generators,
|
||||
OpenIDCallbackChecks,
|
||||
} from "openid-client"
|
||||
import * as jwt from "../../../jwt"
|
||||
|
||||
import type { RequestInternal } from "../.."
|
||||
import type { CookiesOptions, InternalOptions } from "../../types"
|
||||
import type { Cookie } from "../cookie"
|
||||
import { OAuthChecks } from "src/providers"
|
||||
|
||||
/** Returns a signed cookie. */
|
||||
export async function signCookie(
|
||||
type: keyof CookiesOptions,
|
||||
value: string,
|
||||
maxAge: number,
|
||||
options: InternalOptions<"oauth">
|
||||
): Promise<Cookie> {
|
||||
const { cookies, logger } = options
|
||||
|
||||
logger.debug(`CREATE_${type.toUpperCase()}`, { value, maxAge })
|
||||
|
||||
const expires = new Date()
|
||||
expires.setTime(expires.getTime() + maxAge * 1000)
|
||||
return {
|
||||
name: cookies[type].name,
|
||||
value: await jwt.encode({ ...options.jwt, maxAge, token: { value } }),
|
||||
options: { ...cookies[type].options, expires },
|
||||
}
|
||||
}
|
||||
|
||||
const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
|
||||
export const PKCE_CODE_CHALLENGE_METHOD = "S256"
|
||||
export const pkce = {
|
||||
async create(
|
||||
options: InternalOptions<"oauth">,
|
||||
cookies: Cookie[],
|
||||
resParams: AuthorizationParameters
|
||||
) {
|
||||
if (!options.provider?.checks?.includes("pkce")) return
|
||||
const code_verifier = generators.codeVerifier()
|
||||
const value = generators.codeChallenge(code_verifier)
|
||||
resParams.code_challenge = value
|
||||
resParams.code_challenge_method = PKCE_CODE_CHALLENGE_METHOD
|
||||
|
||||
const maxAge =
|
||||
options.cookies.pkceCodeVerifier.options.maxAge ?? PKCE_MAX_AGE
|
||||
|
||||
cookies.push(
|
||||
await signCookie("pkceCodeVerifier", code_verifier, maxAge, options)
|
||||
)
|
||||
},
|
||||
/**
|
||||
* Returns code_verifier if the provider is configured to use PKCE,
|
||||
* and clears the container cookie afterwards.
|
||||
* An error is thrown if the code_verifier is missing or invalid.
|
||||
* @see https://www.rfc-editor.org/rfc/rfc7636
|
||||
* @see https://danielfett.de/2020/05/16/pkce-vs-nonce-equivalent-or-not/#pkce
|
||||
*/
|
||||
async use(
|
||||
cookies: RequestInternal["cookies"],
|
||||
resCookies: Cookie[],
|
||||
options: InternalOptions<"oauth">,
|
||||
checks: OAuthChecks
|
||||
): Promise<string | undefined> {
|
||||
if (!options.provider?.checks?.includes("pkce")) return
|
||||
|
||||
const codeVerifier = cookies?.[options.cookies.pkceCodeVerifier.name]
|
||||
|
||||
if (!codeVerifier)
|
||||
throw new TypeError("PKCE code_verifier cookie was missing.")
|
||||
|
||||
const value = (await jwt.decode({
|
||||
...options.jwt,
|
||||
token: codeVerifier,
|
||||
})) as any
|
||||
|
||||
if (!value?.value)
|
||||
throw new TypeError("PKCE code_verifier value could not be parsed.")
|
||||
|
||||
resCookies.push({
|
||||
name: options.cookies.pkceCodeVerifier.name,
|
||||
value: "",
|
||||
options: { ...options.cookies.pkceCodeVerifier.options, maxAge: 0 },
|
||||
})
|
||||
|
||||
checks.code_verifier = value.value
|
||||
},
|
||||
}
|
||||
|
||||
const STATE_MAX_AGE = 60 * 15 // 15 minutes in seconds
|
||||
export const state = {
|
||||
async create(
|
||||
options: InternalOptions<"oauth">,
|
||||
cookies: Cookie[],
|
||||
resParams: AuthorizationParameters
|
||||
) {
|
||||
if (!options.provider.checks?.includes("state")) return
|
||||
const value = generators.state()
|
||||
resParams.state = value
|
||||
const maxAge = options.cookies.state.options.maxAge ?? STATE_MAX_AGE
|
||||
cookies.push(await signCookie("state", value, maxAge, options))
|
||||
},
|
||||
/**
|
||||
* Returns state if the provider is configured to use state,
|
||||
* and clears the container cookie afterwards.
|
||||
* An error is thrown if the state is missing or invalid.
|
||||
* @see https://www.rfc-editor.org/rfc/rfc6749#section-10.12
|
||||
* @see https://www.rfc-editor.org/rfc/rfc6749#section-4.1.1
|
||||
*/
|
||||
async use(
|
||||
cookies: RequestInternal["cookies"],
|
||||
resCookies: Cookie[],
|
||||
options: InternalOptions<"oauth">,
|
||||
checks: OAuthChecks
|
||||
) {
|
||||
if (!options.provider.checks?.includes("state")) return
|
||||
|
||||
const state = cookies?.[options.cookies.state.name]
|
||||
|
||||
if (!state) throw new TypeError("State cookie was missing.")
|
||||
|
||||
const value = (await jwt.decode({ ...options.jwt, token: state })) as any
|
||||
|
||||
if (!value?.value) throw new TypeError("State value could not be parsed.")
|
||||
|
||||
resCookies.push({
|
||||
name: options.cookies.state.name,
|
||||
value: "",
|
||||
options: { ...options.cookies.state.options, maxAge: 0 },
|
||||
})
|
||||
|
||||
checks.state = value.value
|
||||
},
|
||||
}
|
||||
|
||||
const NONCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
|
||||
export const nonce = {
|
||||
async create(
|
||||
options: InternalOptions<"oauth">,
|
||||
cookies: Cookie[],
|
||||
resParams: AuthorizationParameters
|
||||
) {
|
||||
if (!options.provider.checks?.includes("nonce")) return
|
||||
const value = generators.nonce()
|
||||
resParams.nonce = value
|
||||
const maxAge = options.cookies.nonce.options.maxAge ?? NONCE_MAX_AGE
|
||||
cookies.push(await signCookie("nonce", value, maxAge, options))
|
||||
},
|
||||
/**
|
||||
* Returns nonce if the provider is configured to use nonce,
|
||||
* and clears the container cookie afterwards.
|
||||
* An error is thrown if the nonce is missing or invalid.
|
||||
* @see https://openid.net/specs/openid-connect-core-1_0.html#NonceNotes
|
||||
* @see https://danielfett.de/2020/05/16/pkce-vs-nonce-equivalent-or-not/#nonce
|
||||
*/
|
||||
async use(
|
||||
cookies: RequestInternal["cookies"],
|
||||
resCookies: Cookie[],
|
||||
options: InternalOptions<"oauth">,
|
||||
checks: OpenIDCallbackChecks
|
||||
): Promise<string | undefined> {
|
||||
if (!options.provider?.checks?.includes("nonce")) return
|
||||
|
||||
const nonce = cookies?.[options.cookies.nonce.name]
|
||||
if (!nonce) throw new TypeError("Nonce cookie was missing.")
|
||||
|
||||
const value = (await jwt.decode({ ...options.jwt, token: nonce })) as any
|
||||
|
||||
if (!value?.value) throw new TypeError("Nonce value could not be parsed.")
|
||||
|
||||
resCookies.push({
|
||||
name: options.cookies.nonce.name,
|
||||
value: "",
|
||||
options: { ...options.cookies.nonce.options, maxAge: 0 },
|
||||
})
|
||||
|
||||
checks.nonce = value.value
|
||||
},
|
||||
}
|
||||
@@ -69,3 +69,5 @@ export function oAuth1Client(options: InternalOptions<"oauth">) {
|
||||
}
|
||||
return oauth1Client
|
||||
}
|
||||
|
||||
export const oAuth1TokenStore = new Map()
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
import * as jwt from "../../../jwt"
|
||||
import { generators } from "openid-client"
|
||||
import type { InternalOptions } from "../../types"
|
||||
import type { Cookie } from "../cookie"
|
||||
|
||||
const NONCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
|
||||
|
||||
/**
|
||||
* Returns nonce if the provider supports it
|
||||
* and saves it in a cookie */
|
||||
export async function createNonce(options: InternalOptions<"oauth">): Promise<
|
||||
| undefined
|
||||
| {
|
||||
value: string
|
||||
cookie: Cookie
|
||||
}
|
||||
> {
|
||||
const { cookies, logger, provider } = options
|
||||
if (!provider.checks?.includes("nonce")) {
|
||||
// Provider does not support nonce, return nothing.
|
||||
return
|
||||
}
|
||||
|
||||
const nonce = generators.nonce()
|
||||
|
||||
const expires = new Date()
|
||||
expires.setTime(expires.getTime() + NONCE_MAX_AGE * 1000)
|
||||
|
||||
// Encrypt nonce and save it to an encrypted cookie
|
||||
const encryptedNonce = await jwt.encode({
|
||||
...options.jwt,
|
||||
maxAge: NONCE_MAX_AGE,
|
||||
token: { nonce },
|
||||
})
|
||||
|
||||
logger.debug("CREATE_ENCRYPTED_NONCE", {
|
||||
nonce,
|
||||
maxAge: NONCE_MAX_AGE,
|
||||
})
|
||||
|
||||
return {
|
||||
cookie: {
|
||||
name: cookies.nonce.name,
|
||||
value: encryptedNonce,
|
||||
options: { ...cookies.nonce.options, expires },
|
||||
},
|
||||
value: nonce,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns nonce from if the provider supports nonce,
|
||||
* and clears the container cookie afterwards.
|
||||
*/
|
||||
export async function useNonce(
|
||||
nonce: string | undefined,
|
||||
options: InternalOptions<"oauth">
|
||||
): Promise<{ value: string; cookie: Cookie } | undefined> {
|
||||
const { cookies, provider } = options
|
||||
|
||||
if (!provider?.checks?.includes("nonce") || !nonce) {
|
||||
return
|
||||
}
|
||||
|
||||
const value = (await jwt.decode({...options.jwt, token: nonce })) as any
|
||||
|
||||
return {
|
||||
value: value?.nonce ?? undefined,
|
||||
cookie: {
|
||||
name: cookies.nonce.name,
|
||||
value: "",
|
||||
options: { ...cookies.nonce.options, maxAge: 0 },
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import * as jwt from "../../../jwt"
|
||||
import { generators } from "openid-client"
|
||||
import type { InternalOptions } from "../../types"
|
||||
import type { Cookie } from "../cookie"
|
||||
|
||||
const PKCE_CODE_CHALLENGE_METHOD = "S256"
|
||||
const PKCE_MAX_AGE = 60 * 15 // 15 minutes in seconds
|
||||
|
||||
/**
|
||||
* 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)
|
||||
|
||||
const maxAge = cookies.pkceCodeVerifier.options.maxAge ?? PKCE_MAX_AGE
|
||||
|
||||
const expires = new Date()
|
||||
expires.setTime(expires.getTime() + maxAge * 1000)
|
||||
|
||||
// Encrypt code_verifier and save it to an encrypted cookie
|
||||
const encryptedCodeVerifier = await jwt.encode({
|
||||
...options.jwt,
|
||||
maxAge,
|
||||
token: { code_verifier },
|
||||
})
|
||||
|
||||
logger.debug("CREATE_PKCE_CHALLENGE_VERIFIER", {
|
||||
code_challenge,
|
||||
code_challenge_method: PKCE_CODE_CHALLENGE_METHOD,
|
||||
code_verifier,
|
||||
maxAge,
|
||||
})
|
||||
|
||||
return {
|
||||
code_challenge,
|
||||
code_challenge_method: PKCE_CODE_CHALLENGE_METHOD,
|
||||
cookie: {
|
||||
name: cookies.pkceCodeVerifier.name,
|
||||
value: encryptedCodeVerifier,
|
||||
options: { ...cookies.pkceCodeVerifier.options, expires },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns code_verifier if provider uses PKCE,
|
||||
* 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
|
||||
|
||||
if (!provider?.checks?.includes("pkce") || !codeVerifier) {
|
||||
return
|
||||
}
|
||||
|
||||
const pkce = (await jwt.decode({
|
||||
...options.jwt,
|
||||
token: codeVerifier,
|
||||
})) as any
|
||||
|
||||
return {
|
||||
codeVerifier: pkce?.code_verifier ?? undefined,
|
||||
cookie: {
|
||||
name: cookies.pkceCodeVerifier.name,
|
||||
value: "",
|
||||
options: { ...cookies.pkceCodeVerifier.options, maxAge: 0 },
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import { generators } from "openid-client"
|
||||
|
||||
import type { InternalOptions } from "../../types"
|
||||
import type { Cookie } from "../cookie"
|
||||
|
||||
const STATE_MAX_AGE = 60 * 15 // 15 minutes in seconds
|
||||
|
||||
/** Returns state if the provider supports it */
|
||||
export async function createState(
|
||||
options: InternalOptions<"oauth">
|
||||
): Promise<{ cookie: Cookie; value: string } | undefined> {
|
||||
const { logger, provider, jwt, cookies } = options
|
||||
|
||||
if (!provider.checks?.includes("state")) {
|
||||
// Provider does not support state, return nothing
|
||||
return
|
||||
}
|
||||
|
||||
const state = generators.state()
|
||||
const maxAge = cookies.state.options.maxAge ?? STATE_MAX_AGE
|
||||
|
||||
const encodedState = await jwt.encode({
|
||||
...jwt,
|
||||
maxAge,
|
||||
token: { state },
|
||||
})
|
||||
|
||||
logger.debug("CREATE_STATE", { state, maxAge })
|
||||
|
||||
const expires = new Date()
|
||||
expires.setTime(expires.getTime() + maxAge * 1000)
|
||||
return {
|
||||
value: state,
|
||||
cookie: {
|
||||
name: cookies.state.name,
|
||||
value: encodedState,
|
||||
options: { ...cookies.state.options, expires },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns state from if the provider supports states,
|
||||
* and clears the container cookie afterwards.
|
||||
*/
|
||||
export async function useState(
|
||||
state: string | undefined,
|
||||
options: InternalOptions<"oauth">
|
||||
): Promise<{ value: string; cookie: Cookie } | undefined> {
|
||||
const { cookies, provider, jwt } = options
|
||||
|
||||
if (!provider.checks?.includes("state") || !state) return
|
||||
|
||||
const value = (await jwt.decode({ ...options.jwt, token: state })) as any
|
||||
|
||||
return {
|
||||
value: value?.state ?? undefined,
|
||||
cookie: {
|
||||
name: cookies.state.name,
|
||||
value: "",
|
||||
options: { ...cookies.pkceCodeVerifier.options, maxAge: 0 },
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -102,8 +102,8 @@ export default function ErrorPage(props: ErrorProps) {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{theme?.logo && <img src={theme.logo} alt="Logo" className="logo" />}
|
||||
<div className="card">
|
||||
{theme?.logo && <img src={theme.logo} alt="Logo" className="logo" />}
|
||||
<h1>{heading}</h1>
|
||||
<div className="message">{message}</div>
|
||||
{signin}
|
||||
|
||||
@@ -49,6 +49,13 @@ export default function SigninPage(props: SignInServerPageParams) {
|
||||
return false
|
||||
})
|
||||
|
||||
if (typeof document !== "undefined" && theme.buttonText) {
|
||||
document.documentElement.style.setProperty(
|
||||
"--button-text-color",
|
||||
theme.buttonText
|
||||
)
|
||||
}
|
||||
|
||||
if (typeof document !== "undefined" && theme.brandColor) {
|
||||
document.documentElement.style.setProperty(
|
||||
"--brand-color",
|
||||
@@ -88,8 +95,19 @@ export default function SigninPage(props: SignInServerPageParams) {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{theme.logo && <img src={theme.logo} alt="Logo" className="logo" />}
|
||||
{theme.buttonText && (
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
:root {
|
||||
--button-text-color: ${theme.buttonText}
|
||||
}
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="card">
|
||||
{theme.logo && <img src={theme.logo} alt="Logo" className="logo" />}
|
||||
{error && (
|
||||
<div className="error">
|
||||
<p>{error}</p>
|
||||
@@ -164,7 +182,7 @@ export default function SigninPage(props: SignInServerPageParams) {
|
||||
placeholder="email@example.com"
|
||||
required
|
||||
/>
|
||||
<button type="submit">Sign in with {provider.name}</button>
|
||||
<button id="submitButton" type="submit">Sign in with {provider.name}</button>
|
||||
</form>
|
||||
)}
|
||||
{provider.type === "credentials" && (
|
||||
|
||||
@@ -23,13 +23,24 @@ export default function SignoutPage(props: SignoutProps) {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{theme.logo && <img src={theme.logo} alt="Logo" className="logo" />}
|
||||
{theme.buttonText && (
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
:root {
|
||||
--button-text-color: ${theme.buttonText}
|
||||
}
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="card">
|
||||
{theme.logo && <img src={theme.logo} alt="Logo" className="logo" />}
|
||||
<h1>Signout</h1>
|
||||
<p>Are you sure you want to sign out?</p>
|
||||
<form action={`${url}/signout`} method="POST">
|
||||
<input type="hidden" name="csrfToken" value={csrfToken} />
|
||||
<button type="submit">Sign out</button>
|
||||
<button id="submitButton" type="submit">Sign out</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,8 +22,8 @@ export default function VerifyRequestPage(props: VerifyRequestPageProps) {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{theme.logo && <img src={theme.logo} alt="Logo" className="logo" />}
|
||||
<div className="card">
|
||||
{theme.logo && <img src={theme.logo} alt="Logo" className="logo" />}
|
||||
<h1>Check your email</h1>
|
||||
<p>A sign in link has been sent to your email address.</p>
|
||||
<p>
|
||||
|
||||
@@ -465,7 +465,7 @@ export interface SessionOptions {
|
||||
* However, you can specify your own custom string (such as CUID) to be used.
|
||||
* @default `randomUUID` or `randomBytes.toHex` depending on the Node.js version
|
||||
*/
|
||||
generateSessionToken: () => string
|
||||
generateSessionToken: () => Awaitable<string>
|
||||
}
|
||||
|
||||
export interface DefaultUser {
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
|
||||
.__next-auth-theme-auto,
|
||||
.__next-auth-theme-light {
|
||||
--color-background: #fff;
|
||||
--color-background: #ececec;
|
||||
--color-background-card: #fff;
|
||||
--color-text: #000;
|
||||
--color-primary: #444;
|
||||
--color-control-border: #bbb;
|
||||
@@ -18,7 +19,8 @@
|
||||
}
|
||||
|
||||
.__next-auth-theme-dark {
|
||||
--color-background: #000;
|
||||
--color-background: #161b22;
|
||||
--color-background-card: #0d1117;
|
||||
--color-text: #fff;
|
||||
--color-primary: #ccc;
|
||||
--color-control-border: #555;
|
||||
@@ -29,7 +31,8 @@
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.__next-auth-theme-auto {
|
||||
--color-background: #000;
|
||||
--color-background: #161b22;
|
||||
--color-background-card: #0d1117;
|
||||
--color-text: #fff;
|
||||
--color-primary: #ccc;
|
||||
--color-control-border: #555;
|
||||
@@ -78,10 +81,9 @@ input[type] {
|
||||
width: 100%;
|
||||
padding: 0.5rem 1rem;
|
||||
border: var(--border-width) solid var(--color-control-border);
|
||||
background: var(--color-background);
|
||||
background: var(--color-background-card);
|
||||
font-size: 1rem;
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: inset 0 0.1rem 0.2rem rgba(0, 0, 0, 0.2);
|
||||
color: var(--color-text);
|
||||
|
||||
&:focus {
|
||||
@@ -107,41 +109,39 @@ a.button {
|
||||
}
|
||||
}
|
||||
|
||||
button span {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
button,
|
||||
a.button {
|
||||
margin: 0 0 0.75rem 0;
|
||||
padding: 0.75rem 1rem;
|
||||
color: var(--provider-color, var(--color-primary));
|
||||
background-color: var(--provider-bg, var(--color-background));
|
||||
background-color: var(--provider-bg, var(--color-background-card));
|
||||
font-size: 1.1rem;
|
||||
min-height: 62px;
|
||||
border-color: rgba(0, 0, 0, 0.1);
|
||||
border-radius: var(--border-radius);
|
||||
transition: all 0.1s ease-in-out;
|
||||
box-shadow: #000 0px 0px 0px 0px, #000 0px 0px 0px 0px,
|
||||
rgba(0, 0, 0, 0.2) 0px 10px 15px -3px, rgba(0, 0, 0, 0.1) 0px 4px 6px -4px;
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:has(img) {
|
||||
justify-content: unset;
|
||||
span {
|
||||
flex-grow: 1;
|
||||
}
|
||||
@media (max-width: 450px) {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
&:active {
|
||||
box-shadow: 0 0.15rem 0.3rem rgba(0, 0, 0, 0.15),
|
||||
inset 0 0.1rem 0.2rem var(--color-background),
|
||||
inset 0 -0.1rem 0.1rem rgba(0, 0, 0, 0.1);
|
||||
cursor: pointer;
|
||||
}
|
||||
#provider-logo {
|
||||
width: 25px;
|
||||
display: block;
|
||||
}
|
||||
#provider-logo-dark {
|
||||
@@ -149,20 +149,23 @@ a.button {
|
||||
}
|
||||
}
|
||||
|
||||
#submitButton {
|
||||
color: var(--button-text-color, var(--color-info-text));
|
||||
background-color: var(--brand-color, var(--color-info));
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
button,
|
||||
a.button {
|
||||
color: var(--provider-dark-color, var(--color-primary));
|
||||
background-color: var(--provider-dark-bg, var(--color-background));
|
||||
border: 1px solid #0d0d0d;
|
||||
box-shadow: #000 0px 0px 0px 0px, #ccc 0px 0px 0px 0px,
|
||||
rgba(255, 255, 255, 0.01) 0px 5px 5px -3px,
|
||||
rgba(255, 255, 255, 0.05) 0px 4px 6px -4px;
|
||||
}
|
||||
#provider-logo {
|
||||
display: none !important;
|
||||
}
|
||||
#provider-logo-dark {
|
||||
width: 25px;
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
@@ -189,7 +192,6 @@ a.site {
|
||||
|
||||
> div {
|
||||
text-align: center;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,16 +219,16 @@ a.site {
|
||||
display: block;
|
||||
border: 0;
|
||||
border-top: 1px solid var(--color-seperator);
|
||||
margin: 1.5em auto 0 auto;
|
||||
margin: 2rem auto 1rem auto;
|
||||
overflow: visible;
|
||||
|
||||
&::before {
|
||||
content: "or";
|
||||
background: var(--color-background);
|
||||
background: var(--color-background-card);
|
||||
color: #888;
|
||||
padding: 0 0.4rem;
|
||||
position: relative;
|
||||
top: -0.6rem;
|
||||
top: -0.7rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +236,7 @@ a.site {
|
||||
background: #f5f5f5;
|
||||
font-weight: 500;
|
||||
border-radius: 0.3rem;
|
||||
background: var(--color-info);
|
||||
background: var(--color-error);
|
||||
|
||||
p {
|
||||
text-align: left;
|
||||
@@ -260,25 +262,26 @@ a.site {
|
||||
max-width: 300px;
|
||||
}
|
||||
}
|
||||
.signout {
|
||||
.message {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: inline-block;
|
||||
margin-top: 100px;
|
||||
max-width: 300px;
|
||||
max-height: 150px;
|
||||
max-width: 150px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 25px;
|
||||
max-height: 70px;
|
||||
}
|
||||
|
||||
.card {
|
||||
max-width: max-content;
|
||||
border: 1px solid var(--color-control-border);
|
||||
border-radius: 5px;
|
||||
@media screen and (min-width: 450px) {
|
||||
width: 350px;
|
||||
}
|
||||
@media screen and (max-width: 450px) {
|
||||
width: 200px;
|
||||
}
|
||||
margin: 20px 0 20px 0;
|
||||
background-color: var(--color-background-card);
|
||||
border-radius: 30px;
|
||||
padding: 20px 50px;
|
||||
margin: 50px auto;
|
||||
|
||||
.header {
|
||||
color: var(--color-primary);
|
||||
@@ -286,5 +289,5 @@ a.site {
|
||||
}
|
||||
|
||||
.section-header {
|
||||
color: var(--brand-color, var(--color-text));
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@ function NextAuth(
|
||||
|
||||
export default NextAuth
|
||||
|
||||
let experimentalWarningShown = false
|
||||
let experimentalRSCWarningShown = false
|
||||
|
||||
type GetServerSessionOptions = Partial<Omit<AuthOptions, "callbacks">> & {
|
||||
@@ -91,28 +90,18 @@ type GetServerSessionOptions = Partial<Omit<AuthOptions, "callbacks">> & {
|
||||
}
|
||||
}
|
||||
|
||||
export async function unstable_getServerSession<
|
||||
type GetServerSessionParams<O extends GetServerSessionOptions> =
|
||||
| [GetServerSidePropsContext["req"], GetServerSidePropsContext["res"], O]
|
||||
| [NextApiRequest, NextApiResponse, O]
|
||||
| [O]
|
||||
| []
|
||||
|
||||
export async function getServerSession<
|
||||
O extends GetServerSessionOptions,
|
||||
R = O["callbacks"] extends { session: (...args: any[]) => infer U }
|
||||
? U
|
||||
: Session
|
||||
>(
|
||||
...args:
|
||||
| [GetServerSidePropsContext["req"], GetServerSidePropsContext["res"], O]
|
||||
| [NextApiRequest, NextApiResponse, O]
|
||||
| [O]
|
||||
| []
|
||||
): Promise<R | 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
|
||||
}
|
||||
|
||||
>(...args: GetServerSessionParams<O>): Promise<R | null> {
|
||||
const isRSC = args.length === 0 || args.length === 1
|
||||
if (
|
||||
!experimentalRSCWarningShown &&
|
||||
@@ -121,8 +110,8 @@ export async function unstable_getServerSession<
|
||||
) {
|
||||
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}`,
|
||||
"\n`getServerSession` is used in a React Server Component.",
|
||||
`\nhttps://next-auth.js.org/configuration/nextjs#getServerSession}`,
|
||||
`\nhttps://next-auth.js.org/warnings#EXPERIMENTAL_API`
|
||||
)
|
||||
experimentalRSCWarningShown = true
|
||||
@@ -178,6 +167,25 @@ export async function unstable_getServerSession<
|
||||
return null
|
||||
}
|
||||
|
||||
let deprecatedWarningShown = false
|
||||
|
||||
/** @deprecated renamed to `getServerSession` */
|
||||
export async function unstable_getServerSession<
|
||||
O extends GetServerSessionOptions,
|
||||
R = O["callbacks"] extends { session: (...args: any[]) => infer U }
|
||||
? U
|
||||
: Session
|
||||
>(...args: GetServerSessionParams<O>): Promise<R | null> {
|
||||
if (!deprecatedWarningShown && process.env.NODE_ENV !== "production") {
|
||||
console.warn(
|
||||
"`unstable_getServerSession` has been renamed to `getServerSession`."
|
||||
)
|
||||
deprecatedWarningShown = true
|
||||
}
|
||||
|
||||
return await getServerSession(...args)
|
||||
}
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace NodeJS {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
/** Extract the host from the environment */
|
||||
export function detectHost(forwardedHost: any) {
|
||||
// if `NEXTAUTH_URL_INTERNAL` is set, it means NextAuth.js is deployed
|
||||
// behind a proxy - we prioritize it over `forwardedHost`.
|
||||
if (process.env.NEXTAUTH_URL_INTERNAL) {
|
||||
return process.env.NEXTAUTH_URL_INTERNAL
|
||||
}
|
||||
// If we detect a Vercel environment, we can trust the host
|
||||
if (process.env.VERCEL ?? process.env.AUTH_TRUST_HOST)
|
||||
return forwardedHost
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as core from "../src/core"
|
||||
import { MissingSecret } from "../src/core/errors"
|
||||
import { unstable_getServerSession } from "../src/next"
|
||||
import { getServerSession } from "../src/next"
|
||||
import { mockLogger } from "./lib"
|
||||
|
||||
const originalWarn = console.warn
|
||||
@@ -27,7 +27,7 @@ afterEach(() => {
|
||||
describe("Treat secret correctly", () => {
|
||||
it("Read from NEXTAUTH_SECRET", async () => {
|
||||
process.env.NEXTAUTH_SECRET = "secret"
|
||||
await unstable_getServerSession(req, res, { providers: [], logger })
|
||||
await getServerSession(req, res, { providers: [], logger })
|
||||
|
||||
expect(logger.error).toBeCalledTimes(0)
|
||||
expect(logger.error).not.toBeCalledWith("NO_SECRET")
|
||||
@@ -36,7 +36,7 @@ describe("Treat secret correctly", () => {
|
||||
})
|
||||
|
||||
it("Read from options.secret", async () => {
|
||||
await unstable_getServerSession(req, res, {
|
||||
await getServerSession(req, res, {
|
||||
providers: [],
|
||||
logger,
|
||||
secret: "secret",
|
||||
@@ -51,30 +51,12 @@ describe("Treat secret correctly", () => {
|
||||
"There is a problem with the server configuration. Check the server logs for more information."
|
||||
)
|
||||
await expect(
|
||||
unstable_getServerSession(req, res, { providers: [], logger })
|
||||
getServerSession(req, res, { providers: [], logger })
|
||||
).rejects.toThrowError(configError)
|
||||
|
||||
expect(logger.error).toBeCalledTimes(1)
|
||||
expect(logger.error).toBeCalledWith("NO_SECRET", expect.any(MissingSecret))
|
||||
})
|
||||
|
||||
it("Only logs warning once and in development", async () => {
|
||||
process.env.NEXTAUTH_SECRET = "secret"
|
||||
// Expect console.warn to NOT be called due to NODE_ENV=production
|
||||
await unstable_getServerSession(req, res, { providers: [], logger })
|
||||
expect(console.warn).toBeCalledTimes(0)
|
||||
|
||||
// Expect console.warn to be called ONCE due to NODE_ENV=development
|
||||
// @ts-expect-error
|
||||
process.env.NODE_ENV = "development"
|
||||
await unstable_getServerSession(req, res, { providers: [], logger })
|
||||
expect(console.warn).toBeCalledTimes(1)
|
||||
|
||||
// Expect console.warn to be still only be called ONCE
|
||||
await unstable_getServerSession(req, res, { providers: [], logger })
|
||||
expect(console.warn).toBeCalledTimes(1)
|
||||
delete process.env.NEXTAUTH_SECRET
|
||||
})
|
||||
})
|
||||
|
||||
describe("Return correct data", () => {
|
||||
@@ -87,7 +69,7 @@ describe("Return correct data", () => {
|
||||
// @ts-expect-error
|
||||
spy.mockReturnValue({ body: {} })
|
||||
|
||||
const session = await unstable_getServerSession(req, res, {
|
||||
const session = await getServerSession(req, res, {
|
||||
providers: [],
|
||||
logger,
|
||||
secret: "secret",
|
||||
@@ -113,7 +95,7 @@ describe("Return correct data", () => {
|
||||
// @ts-expect-error
|
||||
spy.mockReturnValue(mockedResponse)
|
||||
|
||||
const session = await unstable_getServerSession(req, res, {
|
||||
const session = await getServerSession(req, res, {
|
||||
providers: [],
|
||||
logger,
|
||||
secret: "secret",
|
||||
|
||||
83
pnpm-lock.yaml
generated
83
pnpm-lock.yaml
generated
@@ -447,10 +447,10 @@ importers:
|
||||
'@babel/preset-env': ^7.18.2
|
||||
'@babel/preset-react': ^7.17.12
|
||||
'@babel/preset-typescript': ^7.17.12
|
||||
'@babel/runtime': ^7.16.3
|
||||
'@babel/runtime': ^7.20.13
|
||||
'@edge-runtime/jest-environment': 1.1.0-beta.35
|
||||
'@next-auth/tsconfig': workspace:*
|
||||
'@panva/hkdf': ^1.0.1
|
||||
'@panva/hkdf': ^1.0.2
|
||||
'@swc/core': ^1.2.198
|
||||
'@swc/jest': ^0.2.21
|
||||
'@testing-library/dom': ^8.13.0
|
||||
@@ -473,11 +473,11 @@ importers:
|
||||
jest: ^28.1.1
|
||||
jest-environment-jsdom: ^28.1.1
|
||||
jest-watch-typeahead: ^1.1.0
|
||||
jose: ^4.9.3
|
||||
jose: ^4.11.4
|
||||
msw: ^0.42.3
|
||||
next: 13.0.6
|
||||
oauth: ^0.9.15
|
||||
openid-client: ^5.1.0
|
||||
openid-client: ^5.4.0
|
||||
postcss: ^8.4.14
|
||||
postcss-cli: ^9.1.0
|
||||
postcss-nested: ^5.0.6
|
||||
@@ -488,12 +488,12 @@ importers:
|
||||
uuid: ^8.3.2
|
||||
whatwg-fetch: ^3.6.2
|
||||
dependencies:
|
||||
'@babel/runtime': 7.18.3
|
||||
'@babel/runtime': 7.20.13
|
||||
'@panva/hkdf': 1.0.2
|
||||
cookie: 0.5.0
|
||||
jose: 4.11.0
|
||||
jose: 4.11.4
|
||||
oauth: 0.9.15
|
||||
openid-client: 5.1.6
|
||||
openid-client: 5.4.0
|
||||
preact: 10.8.2
|
||||
preact-render-to-string: 5.2.0_preact@10.8.2
|
||||
uuid: 8.3.2
|
||||
@@ -4996,22 +4996,22 @@ packages:
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
core-js-pure: 3.26.0
|
||||
regenerator-runtime: 0.13.10
|
||||
regenerator-runtime: 0.13.11
|
||||
dev: true
|
||||
|
||||
/@babel/runtime/7.18.3:
|
||||
resolution: {integrity: sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
regenerator-runtime: 0.13.9
|
||||
|
||||
/@babel/runtime/7.20.1:
|
||||
resolution: {integrity: sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
regenerator-runtime: 0.13.10
|
||||
regenerator-runtime: 0.13.11
|
||||
dev: true
|
||||
|
||||
/@babel/runtime/7.20.13:
|
||||
resolution: {integrity: sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
regenerator-runtime: 0.13.11
|
||||
|
||||
/@babel/template/7.16.7:
|
||||
resolution: {integrity: sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -8245,7 +8245,7 @@ packages:
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.16.7
|
||||
'@babel/runtime': 7.18.3
|
||||
'@babel/runtime': 7.20.13
|
||||
'@types/aria-query': 4.2.2
|
||||
aria-query: 5.0.0
|
||||
chalk: 4.1.2
|
||||
@@ -8258,7 +8258,7 @@ packages:
|
||||
resolution: {integrity: sha512-Gy+IoFutbMQcky0k+bqqumXZ1cTGswLsFqmNLzNdSKkU9KGV2u9oXhukCbbJ9/LRPKiqwxEE8VpV/+YZlfkPUA==}
|
||||
engines: {node: '>=8', npm: '>=6', yarn: '>=1'}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.18.3
|
||||
'@babel/runtime': 7.20.13
|
||||
'@types/testing-library__jest-dom': 5.14.5
|
||||
aria-query: 5.0.0
|
||||
chalk: 3.0.0
|
||||
@@ -8285,7 +8285,7 @@ packages:
|
||||
react-test-renderer:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@babel/runtime': 7.18.3
|
||||
'@babel/runtime': 7.20.13
|
||||
'@types/react': 18.0.15
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0_react@18.2.0
|
||||
@@ -8299,7 +8299,7 @@ packages:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.18.3
|
||||
'@babel/runtime': 7.20.13
|
||||
'@testing-library/dom': 8.14.0
|
||||
'@types/react-dom': 18.0.6
|
||||
react: 18.2.0
|
||||
@@ -15236,7 +15236,7 @@ packages:
|
||||
/history/4.10.1:
|
||||
resolution: {integrity: sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.1
|
||||
'@babel/runtime': 7.20.13
|
||||
loose-envify: 1.4.0
|
||||
resolve-pathname: 3.0.0
|
||||
tiny-invariant: 1.2.0
|
||||
@@ -17659,12 +17659,8 @@ packages:
|
||||
valid-url: 1.0.9
|
||||
dev: true
|
||||
|
||||
/jose/4.11.0:
|
||||
resolution: {integrity: sha512-wLe+lJHeG8Xt6uEubS4x0LVjS/3kXXu9dGoj9BNnlhYq7Kts0Pbb2pvv5KiI0yaKH/eaiR0LUOBhOVo9ktd05A==}
|
||||
dev: false
|
||||
|
||||
/jose/4.11.1:
|
||||
resolution: {integrity: sha512-YRv4Tk/Wlug8qicwqFNFVEZSdbROCHRAC6qu/i0dyNKr5JQdoa2pIGoS04lLO/jXQX7Z9omoNewYIVIxqZBd9Q==}
|
||||
/jose/4.11.4:
|
||||
resolution: {integrity: sha512-94FdcR8felat4vaTJyL/WVdtlWLlsnLMZP8v+A0Vru18K3bQ22vn7TtpVh3JlgBFNIlYOUlGqwp/MjRPOnIyCQ==}
|
||||
dev: false
|
||||
|
||||
/js-beautify/1.14.4:
|
||||
@@ -18839,7 +18835,7 @@ packages:
|
||||
prop-types: ^15.0.0
|
||||
react: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.1
|
||||
'@babel/runtime': 7.20.13
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
tiny-warning: 1.0.3
|
||||
@@ -19214,7 +19210,7 @@ packages:
|
||||
/neo4j-driver/4.4.6:
|
||||
resolution: {integrity: sha512-KzTEQ/PYuaVkeEuQmr8jZm0cFLlK/zAAMe3IkhxWj56Tgwn2nT2RASPrqwvyEVfk8jg6xS4U1Fx2VABmkO4cdQ==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.18.3
|
||||
'@babel/runtime': 7.20.13
|
||||
neo4j-driver-bolt-connection: 4.4.6
|
||||
neo4j-driver-core: 4.4.6
|
||||
rxjs: 6.6.7
|
||||
@@ -19680,11 +19676,10 @@ packages:
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/openid-client/5.1.6:
|
||||
resolution: {integrity: sha512-HTFaXWdUHvLFw4GaEMgC0jXYBgpjgzQQNHW1pZsSqJorSgrXzxJ+4u/LWCGaClDEse5HLjXRV+zU5Bn3OefiZw==}
|
||||
engines: {node: ^12.19.0 || ^14.15.0 || ^16.13.0}
|
||||
/openid-client/5.4.0:
|
||||
resolution: {integrity: sha512-hgJa2aQKcM2hn3eyVtN12tEA45ECjTJPXCgUh5YzTzy9qwapCvmDTVPWOcWVL0d34zeQoQ/hbG9lJhl3AYxJlQ==}
|
||||
dependencies:
|
||||
jose: 4.11.1
|
||||
jose: 4.11.4
|
||||
lru-cache: 6.0.0
|
||||
object-hash: 2.2.0
|
||||
oidc-token-hash: 5.0.1
|
||||
@@ -21606,7 +21601,7 @@ packages:
|
||||
peerDependencies:
|
||||
react: '>=16.13.1'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.18.3
|
||||
'@babel/runtime': 7.20.13
|
||||
react: 18.2.0
|
||||
dev: true
|
||||
|
||||
@@ -21624,7 +21619,7 @@ packages:
|
||||
react: ^16.6.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.6.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.1
|
||||
'@babel/runtime': 7.20.13
|
||||
invariant: 2.2.4
|
||||
prop-types: 15.8.1
|
||||
react: 18.2.0
|
||||
@@ -21672,7 +21667,7 @@ packages:
|
||||
react-loadable: '*'
|
||||
webpack: '>=4.41.1 || 5.x'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.1
|
||||
'@babel/runtime': 7.20.13
|
||||
react-loadable: /@docusaurus/react-loadable/5.5.2_react@18.2.0
|
||||
webpack: 5.73.0
|
||||
dev: true
|
||||
@@ -21691,7 +21686,7 @@ packages:
|
||||
react: '>=15'
|
||||
react-router: '>=5'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.1
|
||||
'@babel/runtime': 7.20.13
|
||||
react: 18.2.0
|
||||
react-router: 5.3.3_react@18.2.0
|
||||
dev: true
|
||||
@@ -21701,7 +21696,7 @@ packages:
|
||||
peerDependencies:
|
||||
react: '>=15'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.1
|
||||
'@babel/runtime': 7.20.13
|
||||
history: 4.10.1
|
||||
loose-envify: 1.4.0
|
||||
prop-types: 15.8.1
|
||||
@@ -21716,7 +21711,7 @@ packages:
|
||||
peerDependencies:
|
||||
react: '>=15'
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.1
|
||||
'@babel/runtime': 7.20.13
|
||||
history: 4.10.1
|
||||
hoist-non-react-statics: 3.3.2
|
||||
loose-envify: 1.4.0
|
||||
@@ -21735,7 +21730,7 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
dependencies:
|
||||
'@babel/runtime': 7.20.1
|
||||
'@babel/runtime': 7.20.13
|
||||
react: 18.2.0
|
||||
use-composed-ref: 1.3.0_react@18.2.0
|
||||
use-latest: 1.2.1_react@18.2.0
|
||||
@@ -21882,17 +21877,13 @@ packages:
|
||||
resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==}
|
||||
dev: true
|
||||
|
||||
/regenerator-runtime/0.13.10:
|
||||
resolution: {integrity: sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw==}
|
||||
dev: true
|
||||
|
||||
/regenerator-runtime/0.13.9:
|
||||
resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==}
|
||||
/regenerator-runtime/0.13.11:
|
||||
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
|
||||
|
||||
/regenerator-transform/0.15.0:
|
||||
resolution: {integrity: sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.18.3
|
||||
'@babel/runtime': 7.20.13
|
||||
dev: true
|
||||
|
||||
/regexp.prototype.flags/1.4.3:
|
||||
|
||||
Reference in New Issue
Block a user