From a2e5afa16252335a0186fce304ebd466207a38c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bal=C3=A1zs=20Orb=C3=A1n?= Date: Mon, 5 Jul 2021 16:03:55 +0200 Subject: [PATCH] feat(react): make session requireable in `useSession` (#2236) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A living session could be a requirement for specific pages (like dashboards). If it doesn’t exist, the user should be redirected to a page asking them to sign in again. Sometimes, a user might log out by accident, or by deleting cookies on purpose. If that happens (e.g. on a separate tab), then `useSession({ required: true })` should detect the absence of a session cookie and always return a non-nullable Session object type. When `required: true` is set, the default behavior will be to redirect the user to the sign-in page. This can be overridden by an `action()` callback: ```js const session = useSession({ required: true, action() { // .... } }) if (session.status === "Loading") return "Loading or not authenticated..." // session.data is always defined here. ``` Co-authored-by: Kristóf Poduszló Co-authored-by: Lluis Agusti BREAKING CHANGE: The `useSession` hook now returns an object. Here is how to accommodate for this change: ```diff - const [ session, loading ] = useSession() + const { data: session, status } = useSession() + const loading = status === "loading" ``` With the new `status` option, you can test states much more clearly. --- README.md | 38 ++++++++-- app/components/header.js | 4 +- app/pages/_app.js | 21 +---- app/pages/credentials.js | 2 +- app/pages/email.js | 2 +- app/pages/protected.js | 20 ++--- package.json | 3 +- src/client/__tests__/client-provider.test.js | 30 ++++++-- src/client/react.js | 41 +++++++++- src/server/index.js | 1 + src/server/pages/signin.js | 1 + types/internals/react.d.ts | 10 ++- types/react-client.d.ts | 18 +++-- types/tests/react.test.ts | 18 ++++- www/docs/configuration/pages.md | 1 + www/docs/getting-started/client.md | 76 ++++++++++++++----- www/docs/getting-started/example.md | 52 ++++++------- www/docs/getting-started/typescript.md | 4 +- www/docs/tutorials/refresh-token-rotation.md | 2 +- .../securing-pages-and-api-routes.md | 21 +++-- .../tutorials/usage-with-class-components.md | 12 +-- www/src/pages/index.js | 31 +++++--- 22 files changed, 267 insertions(+), 141 deletions(-) diff --git a/README.md b/README.md index 3d963c223e..552339a9d5 100644 --- a/README.md +++ b/README.md @@ -67,15 +67,15 @@ NextAuth.js can be used with or without a database. ### Secure by default -- Promotes the use of passwordless sign in mechanisms -- Designed to be secure by default and encourage best practice for safeguarding user data -- Uses Cross Site Request Forgery Tokens on POST routes (sign in, sign out) +- Promotes the use of passwordless sign-in mechanisms +- Designed to be secure by default and encourage best practices for safeguarding user data +- Uses Cross-Site Request Forgery (CSRF) Tokens on POST routes (sign in, sign out) - Default cookie policy aims for the most restrictive policy appropriate for each cookie - When JSON Web Tokens are enabled, they are signed by default (JWS) with HS512 - Use JWT encryption (JWE) by setting the option `encryption: true` (defaults to A256GCM) - Auto-generates symmetric signing and encryption keys for developer convenience -- Features tab/window syncing and keepalive messages to support short lived sessions -- Attempts to implement the latest guidance published by [Open Web Application Security Project](https://owasp.org/) +- Features tab/window syncing and session polling to support short lived sessions +- Attempts to implement the latest guidance published by [Open Web Application Security Project](https://owasp.org) Advanced options allow you to define your own routines to handle controlling what accounts are allowed to sign in, for encoding and decoding JSON Web Tokens and to set custom cookie security policies and session properties, so you can control who is able to sign in and how often sessions have to be re-validated. @@ -90,6 +90,7 @@ The package at `@types/next-auth` is now deprecated. ### Add API Route ```javascript +// pages/api/auth/[...nextauth].js import NextAuth from "next-auth" import Providers from "next-auth/providers" @@ -113,13 +114,15 @@ export default NextAuth({ }) ``` -### Add React Component +### Add React Hook + +The `useSession()` React Hook in the NextAuth.js client is the easiest way to check if someone is signed in. ```javascript import { useSession, signIn, signOut } from "next-auth/react" export default function Component() { - const [session, loading] = useSession() + const { data: session } = useSession() if (session) { return ( <> @@ -137,7 +140,26 @@ export default function Component() { } ``` -## Acknowledgements +### Share/configure session state + +Use the `` to allows instances of `useSession()` to share the session object across components. It also takes care of keeping the session updated and synced between tabs/windows. + +```jsx title="pages/_app.js" +import { SessionProvider } from "next-auth/react" + +export default function App({ + Component, + pageProps: { session, ...pageProps } +}) { + return ( + + + + ) +} +``` + +## Acknowledgments [NextAuth.js is made possible thanks to all of its contributors.](https://next-auth.js.org/contributors) diff --git a/app/components/header.js b/app/components/header.js index 415cf1e356..90cda15a7b 100644 --- a/app/components/header.js +++ b/app/components/header.js @@ -6,7 +6,7 @@ import styles from "./header.module.css" // component that works on pages which support both client and server side // rendering, and avoids any flash incorrect content on initial page load. export default function Header() { - const [session, loading] = useSession() + const { data: session, status } = useSession() return (
@@ -16,7 +16,7 @@ export default function Header() {

{!session && ( diff --git a/app/pages/_app.js b/app/pages/_app.js index c1a40b5d20..e073fafcbb 100644 --- a/app/pages/_app.js +++ b/app/pages/_app.js @@ -1,31 +1,12 @@ import { SessionProvider } from "next-auth/react" import "./styles.css" -// Use the to improve performance and allow components that call -// `useSession()` anywhere in your application to access the `session` object. export default function App({ Component, pageProps: { session, ...pageProps }, }) { return ( - + ) diff --git a/app/pages/credentials.js b/app/pages/credentials.js index 5f89cdf047..976566659c 100644 --- a/app/pages/credentials.js +++ b/app/pages/credentials.js @@ -21,7 +21,7 @@ export default function Page() { setResponse(response) } - const [session] = useSession() + const { data: session } = useSession() if (session) { return ( diff --git a/app/pages/email.js b/app/pages/email.js index 9f3a079a04..a83dc55078 100644 --- a/app/pages/email.js +++ b/app/pages/email.js @@ -29,7 +29,7 @@ export default function Page() { setResponse(response) } - const [session] = useSession() + const { data: session } = useSession() if (session) { return ( diff --git a/app/pages/protected.js b/app/pages/protected.js index e89c75a522..88951afb77 100644 --- a/app/pages/protected.js +++ b/app/pages/protected.js @@ -1,14 +1,16 @@ import { useState, useEffect } from "react" import { useSession } from "next-auth/react" import Layout from "../components/layout" -import AccessDenied from "../components/access-denied" export default function Page() { - const [session, loading] = useSession() + const { status } = useSession({ + required: true, + }) const [content, setContent] = useState() // Fetch content from protected route useEffect(() => { + if (status === "loading") return const fetchData = async () => { const res = await fetch("/api/examples/protected") const json = await res.json() @@ -17,19 +19,9 @@ export default function Page() { } } fetchData() - }, [session]) + }, [status]) - // When rendering client side don't display anything until loading is complete - if (typeof window !== "undefined" && loading) return null - - // If no session exists, display access denied message - if (!session) { - return ( - - - - ) - } + if (status === "loading") return Loading... // If session exists, display content return ( diff --git a/package.json b/package.json index 11a9a5a0e8..2129458665 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,8 @@ "prepublishOnly": "npm run build", "lint": "eslint .", "lint:fix": "eslint . --fix", - "version:pr": "node ./config/version-pr" + "version:pr": "node ./config/version-pr", + "website": "cd www && npm run start" }, "files": [ "dist", diff --git a/src/client/__tests__/client-provider.test.js b/src/client/__tests__/client-provider.test.js index db372ae047..135983cc8c 100644 --- a/src/client/__tests__/client-provider.test.js +++ b/src/client/__tests__/client-provider.test.js @@ -16,6 +16,22 @@ afterAll(() => { server.close() }) +test("it won't allow to fetch the session in isolation without a session context", () => { + function App() { + useSession() + return null + } + + jest.spyOn(console, "error") + console.error.mockImplementation(() => {}) + + expect(() => render()).toThrow( + "useSession must be wrapped in a SessionProvider" + ) + + console.error.mockRestore() +}) + test("fetches the session once and re-uses it for different consumers", async () => { const sessionRouteCall = jest.fn() @@ -66,21 +82,19 @@ test("when there's an existing session, it won't initialize as loading", async ( function ProviderFlow({ options = {} }) { return ( - <> - - - - - + + + + ) } function SessionConsumer({ testId = 1 }) { - const [session, loading] = useSession() + const { data: session, status } = useSession() return (

- {loading ? "loading" : JSON.stringify(session)} + {status === "loading" ? "loading" : JSON.stringify(session)}
) } diff --git a/src/client/react.js b/src/client/react.js index 02776d8013..ee83a3aabb 100644 --- a/src/client/react.js +++ b/src/client/react.js @@ -43,8 +43,33 @@ const logger = proxyLogger(_logger, __NEXTAUTH.basePath) /** @type {import("types/internals/react").SessionContext} */ const SessionContext = React.createContext() -export function useSession() { - return React.useContext(SessionContext) +export function useSession(options = {}) { + const value = React.useContext(SessionContext) + + if (process.env.NODE_ENV !== "production" && !value) { + throw new Error("useSession must be wrapped in a SessionProvider") + } + + const { required, onUnauthenticated } = options + + const requiredAndNotLoading = required && value.status === "unauthenticated" + + React.useEffect(() => { + if (requiredAndNotLoading) { + const url = `/api/auth/signin?${new URLSearchParams({ + error: "SessionRequired", + callbackUrl: window.location.href, + })}` + if (onUnauthenticated) onUnauthenticated() + else window.location.replace(url) + } + }, [requiredAndNotLoading, onUnauthenticated]) + + if (requiredAndNotLoading) { + return { data: value.data, status: "loading" } + } + + return value } export async function getSession(ctx) { @@ -269,7 +294,17 @@ export function SessionProvider(props) { } }, [props.refetchInterval]) - const value = React.useMemo(() => [session, loading], [session, loading]) + const value = React.useMemo( + () => ({ + data: session, + status: loading + ? "loading" + : session + ? "authenticated" + : "unauthenticated", + }), + [session, loading] + ) return ( {children} diff --git a/src/server/index.js b/src/server/index.js index 195a7d6881..a1cfa06a88 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -210,6 +210,7 @@ async function NextAuthHandler(req, res, userOptions) { "OAuthAccountNotLinked", "EmailSignin", "CredentialsSignin", + "SessionRequired", ].includes(error) ) { return res.redirect(`${baseUrl}${basePath}/signin?error=${error}`) diff --git a/src/server/pages/signin.js b/src/server/pages/signin.js index ebccbd0176..b69a86c03e 100644 --- a/src/server/pages/signin.js +++ b/src/server/pages/signin.js @@ -32,6 +32,7 @@ export default function signin({ EmailSignin: "Check your email inbox.", CredentialsSignin: "Sign in failed. Check the details you provided are correct.", + SessionRequired: "Please sign in to access this page.", default: "Unable to sign in.", } diff --git a/types/internals/react.d.ts b/types/internals/react.d.ts index f83ef121fd..57dd378355 100644 --- a/types/internals/react.d.ts +++ b/types/internals/react.d.ts @@ -26,4 +26,12 @@ export interface NextAuthConfig { _getSession: any } -export type SessionContext = React.Context +export type SessionContextValue = R extends true + ? + | { data: Session; status: "authenticated" } + | { data: null; status: "loading" } + : + | { data: Session; status: "authenticated" } + | { data: null; status: "unauthenticated" | "loading" } + +export type SessionContext = React.Context diff --git a/types/react-client.d.ts b/types/react-client.d.ts index d7ae17ad07..0a3b3ec3f6 100644 --- a/types/react-client.d.ts +++ b/types/react-client.d.ts @@ -2,6 +2,7 @@ import * as React from "react" import { IncomingMessage } from "http" import { Session } from "." import { ProviderType } from "./providers" +import { SessionContextValue } from "internals/react" export interface CtxOrReq { req?: IncomingMessage @@ -17,21 +18,22 @@ export type GetSessionOptions = CtxOrReq & { triggerEvent?: boolean } +export interface UseSessionOptions { + required: R + /** Defaults to `signIn` */ + action?(): void +} + /** * React Hook that gives you access * to the logged in user's session data. * * [Documentation](https://next-auth.js.org/getting-started/client#usesession) */ -export function useSession(): [Session | null, boolean] +export function useSession( + options?: UseSessionOptions +): SessionContextValue -/** - * Can be called client or server side to return a session asynchronously. - * It calls `/api/auth/session` and returns a promise with a session object, - * or null if no session exists. - * - * [Documentation](https://next-auth.js.org/getting-started/client#getsession) - */ export function getSession(options?: GetSessionOptions): Promise /******************* diff --git a/types/tests/react.test.ts b/types/tests/react.test.ts index c866b0b420..2660adc248 100644 --- a/types/tests/react.test.ts +++ b/types/tests/react.test.ts @@ -11,9 +11,25 @@ const clientSession = { expires: "1234", } -// $ExpectType [Session | null, boolean] +/** + * $ExpectType + * | { data: Session; status: "authenticated"; } + * | { data: null; status: "unauthenticated" | "loading"; } + * | { //// data: Session; status: "authenticated"; } + * | { data: null; status: "loading"; } + */ client.useSession() +// $ExpectType { data: Session; status: "authenticated"; } | { data: null; status: "loading"; } +const session = client.useSession({ required: true }) +if (session.status === "loading") { + // $ExpectType null + session.data +} else { + // $ExpectType Session + session.data +} + // $ExpectType Promise client.getSession({ req: nextReq }) diff --git a/www/docs/configuration/pages.md b/www/docs/configuration/pages.md index 2aa1058400..6486987140 100644 --- a/www/docs/configuration/pages.md +++ b/www/docs/configuration/pages.md @@ -48,6 +48,7 @@ The following errors are passed as error query parameters to the default or over - **OAuthAccountNotLinked**: If the email on the account is already linked, but not with this OAuth account - **EmailSignin**: Sending the e-mail with the verification token failed - **CredentialsSignin**: The `authorize` callback returned `null` in the [Credentials provider](/providers/credentials). We don't recommend providing information about which part of the credentials were wrong, as it might be abused by malicious hackers. +- **SessionRequired**: The content of this page requires you to be signed in at all times. See [useSession](/getting-started/client#require-session) for configuration. - **Default**: Catch all, will apply, if none of the above matched Example: `/auth/error?error=Default` diff --git a/www/docs/getting-started/client.md b/www/docs/getting-started/client.md index 664957e92a..dbfb2797fb 100644 --- a/www/docs/getting-started/client.md +++ b/www/docs/getting-started/client.md @@ -34,7 +34,7 @@ You can use the [session callback](/configuration/callbacks#session-callback) to The `useSession()` React Hook in the NextAuth.js client is the easiest way to check if someone is signed in. -It works best when the [``](#sessionprovider) is added to `pages/_app.js`. +Make sure that [``](#sessionprovider) is added to `pages/_app.js`. #### Example @@ -42,9 +42,9 @@ It works best when the [``](#sessionprovider) is added to `page import { useSession } from "next-auth/react" export default function Component() { - const [session, loading] = useSession() + const { data: session, status } = useSession() - if (session) { + if (status === "authenticated") { return

Signed in as {session.user.email}

} @@ -52,6 +52,41 @@ export default function Component() { } ``` +`useSession()` returns an object containing two values: `data` and `status`: + +- **`data`**: This can be three values: [`Session`](https://github.com/nextauthjs/next-auth/blob/8ff4b260143458c5d8a16b80b11d1b93baa0690f/types/index.d.ts#L437-L444) / `undefined` / `null`. + - when the session hasn't been fetched yet, `data` will `undefined` + - in case it failed to retrieve the session, `data` will be `null` + - in case of success, `data` will be [`Session`](https://github.com/nextauthjs/next-auth/blob/8ff4b260143458c5d8a16b80b11d1b93baa0690f/types/index.d.ts#L437-L444). +- **`status`**: enum mapping to three possible session states: `"loading" | "authenticated" | "unauthenticated"` + +### Require session + +Due to the way how Next.js handles `getServerSideProps` and `getInitialProps`, every protected page load has to make a server-side request to check if the session is valid and then generate the requested page (SSR). This increases server load, and if you are good with making the requests from the client, there is an alternative. You can use `useSession` in a way that makes sure you always have a valid session. If after the initial loading state there was no session found, you can define the appropriate action to respond. + +The default behavior is to redirect the user to the sign-in page, from where - after a successful login - they will be sent back to the page they started on. You can also define an `onFail()` callback, if you would like to do something else: + +#### Example + +```jsx title="pages/protected.jsx" +import { useSession } from "next-auth/react" + +export default function Admin() { + const { status } = useSession({ + required: true, + onUnauthenticated() { + // The user is not authenticated, handle it here. + } + }) + + const if (status === "loading") { + return "Loading or not authenticated..." + } + + return "User is logged in" +} +``` + --- ## getSession() @@ -206,7 +241,7 @@ e.g. - `signIn('google', { callbackUrl: 'http://localhost:3000/foo' })` - `signIn('email', { email, callbackUrl: 'http://localhost:3000/foo' })` -The URL must be considered valid by the [redirect callback handler](/configuration/callbacks#redirect-callback). By default it requires the URL to be an absolute URL at the same hostname, or else it will redirect to the homepage. You can define your own [redirect callback](/configuration/callbacks#redirect-callback) to allow other URLs, including supporting relative URLs. +The URL must be considered valid by the [redirect callback handler](/configuration/callbacks#redirect-callback). By default it requires the URL to be an absolute URL at the same host name, or else it will redirect to the homepage. You can define your own [redirect callback](/configuration/callbacks#redirect-callback) to allow other URLs, including supporting relative URLs. #### Using the redirect: false option @@ -248,7 +283,7 @@ e.g. } ``` -#### Additional params +#### Additional parameters It is also possible to pass additional parameters to the `/authorize` endpoint through the third argument of `signIn()`. @@ -256,7 +291,7 @@ See the [Authorization Request OIDC spec](https://openid.net/specs/openid-connec e.g. -- `signIn("identity-server4", null, { prompt: "login" })` _always ask the user to reauthenticate_ +- `signIn("identity-server4", null, { prompt: "login" })` _always ask the user to re-authenticate_ - `signIn("auth0", null, { login_hint: "info@example.com" })` _hints the e-mail address to the provider_ :::note @@ -290,7 +325,7 @@ As with the `signIn()` function, you can specify a `callbackUrl` parameter by pa e.g. `signOut({ callbackUrl: 'http://localhost:3000/foo' })` -The URL must be considered valid by the [redirect callback handler](/configuration/callbacks#redirect-callback). By default this means it must be an absolute URL at the same hostname (or else it will default to the homepage); you can define your own custom [redirect callback](/configuration/callbacks#redirect-callback) to allow other URLs, including supporting relative URLs. +The URL must be considered valid by the [redirect callback handler](/configuration/callbacks#redirect-callback). By default this means it must be an absolute URL at the same host name (or else it will default to the homepage); you can define your own custom [redirect callback](/configuration/callbacks#redirect-callback) to allow other URLs, including supporting relative URLs. #### Using the redirect: false option @@ -299,7 +334,7 @@ If you pass `redirect: false` to `signOut`, the page will not reload. The sessio :::tip If you need to redirect to another page but you want to avoid a page reload, you can try: `const data = await signOut({redirect: false, callbackUrl: "/foo"})` -where `data.url` is the validated url you can redirect the user to without any flicker by using Next.js's `useRouter().push(data.url)` +where `data.url` is the validated URL you can redirect the user to without any flicker by using Next.js's `useRouter().push(data.url)` ::: --- @@ -312,8 +347,8 @@ Using the supplied `` allows instances of `useSession()` to sha import { SessionProvider } from "next-auth/react" export default function App({ - Component, - pageProps: { session, ...pageProps } + Component, + pageProps: { session, ...pageProps }, }) { return ( @@ -341,7 +376,7 @@ export async function getServerSideProps(ctx) { } ``` -If every one of your pages needs to be protected, you can do this in `_app`, otherwise you can do it on a page-by-page basis. Alternatively, you can do per page authentication checks client side, instead of having each auth check be blocking (SSR) by using the method described below in [alternative client session handling](#custom-client-session-handling). +If every one of your pages needs to be protected, you can do this in `_app`, otherwise you can do it on a page-by-page basis. Alternatively, you can do per page authentication checks client side, instead of having each authentication check be blocking (SSR) by using the method described below in [alternative client session handling](#custom-client-session-handling). ### Options @@ -354,10 +389,9 @@ However, if you need to customize the session behavior and/or are using short se ```jsx title="pages/_app.js" import { SessionProvider } from "next-auth/react" - export default function App({ - Component, - pageProps: { session, ...pageProps } + Component, + pageProps: { session, ...pageProps }, }) { return ( @@ -442,7 +476,7 @@ export default function App({ } function Auth({ children }) { - const [session, loading] = useSession() + const { data: session, loading } = useSession() const isUser = !!session?.user React.useEffect(() => { if (loading) return // Do nothing while loading @@ -469,9 +503,9 @@ AdminDashboard.auth = { } ``` -Because of how \_app is done, it won't unnecessarily contact the /api/auth/session endpoint for pages that do not require auth. +Because of how `_app` is written, it won't unnecessarily contact the `/api/auth/session` endpoint for pages that do not require authentication. -More information can be found in the following [Github Issue](https://github.com/nextauthjs/next-auth/issues/1210). +More information can be found in the following [GitHub Issue](https://github.com/nextauthjs/next-auth/issues/1210). ### NextAuth.js + React-Query diff --git a/www/docs/getting-started/example.md b/www/docs/getting-started/example.md index a5b61c3a69..438a975159 100644 --- a/www/docs/getting-started/example.md +++ b/www/docs/getting-started/example.md @@ -33,7 +33,7 @@ export default NextAuth({ }) ``` -All requests to `/api/auth/*` (signin, callback, signout, etc) will automatically be handed by NextAuth.js. +All requests to `/api/auth/*` (`signIn`, callback, `signOut`, etc.) will automatically be handled by NextAuth.js. :::tip See the [options documentation](/configuration/options) for how to configure providers, databases and other options. @@ -41,28 +41,25 @@ See the [options documentation](/configuration/options) for how to configure pro ### Add React Hook -The `useSession()` React Hook in the NextAuth.js client is the easiest way to check if someone is signed in. - -```jsx title="pages/index.js" -import { signIn, signOut, useSession } from "next-auth/react" - -export default function Page() { - const [session, loading] = useSession() - +The [`useSession()`](http://localhost:3000/getting-started/client#usesession) React Hook in the NextAuth.js client is the easiest way to check if someone is signed in. + +```javascript +import { useSession, signIn, signOut } from "next-auth/react" + +export default function Component() { + const { data: session } = useSession() + if (session) { + return ( + <> + Signed in as {session.user.email}
+ + + ) + } return ( <> - {!session && ( - <> - Not signed in
- - - )} - {session && ( - <> - Signed in as {session.user.email}
- - - )} + Not signed in
+ ) } @@ -72,16 +69,17 @@ export default function Page() { You can use the `useSession` hook from anywhere in your application (e.g. in a header component). ::: -### Add session state +### Share/configure session state -To allow session state to be shared between pages - which improves performance, reduces network traffic and avoids component state changes while rendering - you can use the NextAuth.js Provider in `pages/_app.js`. +To be able to use `useSession` first you'll need to expose the session context, [``](http://localhost:3000/getting-started/client#sessionprovider), at the top level of your application: -```jsx title="pages/_app.js" +```javascript +// pages/_app.js import { SessionProvider } from "next-auth/react" export default function App({ - Component, - pageProps: { session, ...pageProps } + Component, + pageProps: { session, ...pageProps }, }) { return ( @@ -91,6 +89,8 @@ export default function App({ } ``` +In this way instances of `useSession` can have access to the session data and status, otherwise they'll throw an error... `` also takes care of keeping the session updated and synced between browser tabs and windows. + :::tip Check out the [client documentation](/getting-started/client) to see how you can improve the user experience and page performance by using the NextAuth.js client. ::: diff --git a/www/docs/getting-started/typescript.md b/www/docs/getting-started/typescript.md index 9996e03eef..81741d65ec 100644 --- a/www/docs/getting-started/typescript.md +++ b/www/docs/getting-started/typescript.md @@ -75,8 +75,8 @@ export default NextAuth({ import { useSession } from "next-auth/react" export default function IndexPage() { - // `session` should match `callbacks.session()` in `NextAuth()` - const [session] = useSession() + // `session` will match the returned value of `callbacks.session()` from `NextAuth()` + const { data: session } = useSession() return ( // Your component diff --git a/www/docs/tutorials/refresh-token-rotation.md b/www/docs/tutorials/refresh-token-rotation.md index 3ecdbf5312..fdce856392 100644 --- a/www/docs/tutorials/refresh-token-rotation.md +++ b/www/docs/tutorials/refresh-token-rotation.md @@ -124,7 +124,7 @@ import { signIn, useSession } from "next-auth/react"; import { useEffect } from "react"; const HomePage() { - const [session] = useSession(); + const { data: session } = useSession(); useEffect(() => { if (session?.error === "RefreshAccessTokenError") { diff --git a/www/docs/tutorials/securing-pages-and-api-routes.md b/www/docs/tutorials/securing-pages-and-api-routes.md index fe50821d49..a6f642f4b5 100644 --- a/www/docs/tutorials/securing-pages-and-api-routes.md +++ b/www/docs/tutorials/securing-pages-and-api-routes.md @@ -21,11 +21,15 @@ If data on a page is fetched using calls to secure API routes - i.e. routes whic import { useSession, getSession } from "next-auth/react" export default function Page() { - const [session, loading] = useSession() + const { data: session, status } = useSession() - if (loading) return null + if (status === "loading") { + return

Loading...

+ } - if (!loading && !session) return

Access Denied

+ if (status === "unauthenticated") { + return

Access Denied

+ } return ( <> @@ -44,9 +48,9 @@ You can protect server side rendered pages using the `getSession()` method. import { useSession, getSession } from "next-auth/react" export default function Page() { - const [session, loading] = useSession() + const { data: session } = useSession() - if (typeof window !== "undefined" && loading) return null + if (typeof window !== "undefined") return null if (session) { return ( @@ -60,15 +64,16 @@ export default function Page() { } export async function getServerSideProps(context) { - const session = await getSession(context) return { - props: { session }, + props: { + session: await getSession(context) + }, } } ``` :::tip -This example assumes you have configured `_app.js` to pass the `session` prop through so that it's immediately available on page load to `useSession`. +When you supply a `session` prop in `_app.js`, `useSession` won't show a loading state, as it'll already have the session available. In this way, you can provide a more seamless user experience. ```js title="pages/_app.js" import { SessionProvider } from "next-auth/react" diff --git a/www/docs/tutorials/usage-with-class-components.md b/www/docs/tutorials/usage-with-class-components.md index c2b5fcf562..3e2007767b 100644 --- a/www/docs/tutorials/usage-with-class-components.md +++ b/www/docs/tutorials/usage-with-class-components.md @@ -11,11 +11,11 @@ If you want to use the `useSession()` hook in your class components you can do s import { useSession } from "next-auth/react" const withSession = (Component) => (props) => { - const [session, loading] = useSession() + const session = useSession() // if the component has a render property, we are good if (Component.prototype.render) { - return + return } // if the passed component is a function component, there is no need for this wrapper @@ -30,7 +30,7 @@ const withSession = (Component) => (props) => { // Usage class ClassComponent extends React.Component { render() { - const { session, loading } = this.props + const { data: session, status } = this.props.session return null } } @@ -44,8 +44,8 @@ const ClassComponentWithSession = withSession(ClassComponent) import { useSession } from "next-auth/react" const UseSession = ({ children }) => { - const [session, loading] = useSession() - return children({ session, loading }) + const session = useSession() + return children(session) } // Usage @@ -53,7 +53,7 @@ class ClassComponent extends React.Component { render() { return ( - {({ session, loading }) => ( + {(session) => (
{JSON.stringify(session, null, 2)}
)}
diff --git a/www/src/pages/index.js b/www/src/pages/index.js index 7a8c58903a..55f1967d6b 100644 --- a/www/src/pages/index.js +++ b/www/src/pages/index.js @@ -193,11 +193,13 @@ function Home() {

- Client /pages/index.js + Client (App) /pages/_app.jsx

- - {reactComponentCode} - + {appCode} +

+ Client (Page) /pages/index.js +

+ {pageCode}
@@ -224,13 +226,24 @@ function Home() { ) } -const reactComponentCode = ` -import { - useSession, signIn, signOut -} from "next-auth/react" +const appCode = ` +import { SessionProvider } from "next-auth/react" + +export default App({ + Component, pageProps: { session, ...pageProps } +}) { + return ( + + + + ) +}`.trim() + +const pageCode = ` +import { useSession, signIn, signOut } from "next-auth/react" export default function Component() { - const [ session, loading ] = useSession() + const { data: session } = useSession() if(session) { return <> Signed in as {session.user.email}