diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 9652598fcb8..4b63e17aaeb 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -475,10 +475,9 @@ export const auth = { items: [ { name: 'Overview', url: '/guides/auth/auth-helpers' }, { name: 'Auth UI', url: '/guides/auth/auth-helpers/auth-ui' }, - { name: 'Next.js (pages)', url: '/guides/auth/auth-helpers/nextjs' }, { - name: 'Next.js (app)', - url: '/guides/auth/auth-helpers/nextjs-server-components', + name: 'Next.js', + url: '/guides/auth/auth-helpers/nextjs', }, { name: 'Remix', url: '/guides/auth/auth-helpers/remix' }, { name: 'SvelteKit', url: '/guides/auth/auth-helpers/sveltekit' }, diff --git a/apps/docs/pages/guides/auth/auth-helpers/nextjs-pages.mdx b/apps/docs/pages/guides/auth/auth-helpers/nextjs-pages.mdx new file mode 100644 index 00000000000..bd745cb610b --- /dev/null +++ b/apps/docs/pages/guides/auth/auth-helpers/nextjs-pages.mdx @@ -0,0 +1,847 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + id: 'nextjs-pages', + title: 'Supabase Auth with Next.js Pages Directory', + description: + 'Authentication helpers for Next.js API routes, middleware, and SSR in the Pages Directory.', + sidebar_label: 'Next.js (pages)', +} + +This submodule provides convenience helpers for implementing user authentication in Next.js applications using the pages directory. + +> Note: As of [Next.js 13.4](https://nextjs.org/blog/next-13-4), the App Router has reached stable status. Check out our guide on using [Auth Helpers with the Next.js App Directory](/docs/guides/auth/auth-helpers/nextjs). + +## Install the Next.js helper library + + + + +```sh +npm install @supabase/auth-helpers-nextjs +``` + +This library supports the following tooling versions: + +- Node.js: `^10.13.0 || >=12.0.0` +- Next.js: `>=10` + +Additionally, install the **React Auth Helpers** for components and hooks that can be used across all React-based frameworks. + +```sh +npm install @supabase/auth-helpers-react +``` + + + + +```sh +yarn add @supabase/auth-helpers-nextjs +``` + +This library supports the following tooling versions: + +- Node.js: `^10.13.0 || >=12.0.0` +- Next.js: `>=10` + +Additionally, install the **React Auth Helpers** for components and hooks that can be used across all React-based frameworks. + +```sh +yarn add @supabase/auth-helpers-react +``` + + + + +## Set up environment variables + +Retrieve your project URL and anon key in your project's [API settings](https://app.supabase.com/project/_/settings/api) in the Dashboard to set up the following environment variables. For local development you can set them in a `.env.local` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/nextjs/.env.local.example). + +```bash title=.env.local +NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL +NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY +``` + +## Basic Setup + + + + +Wrap your `pages/_app.js` component with the `SessionContextProvider` component: + +```jsx title=pages/_app.js +import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' +import { SessionContextProvider } from '@supabase/auth-helpers-react' +import { useState } from 'react' + +function MyApp({ Component, pageProps }) { + // Create a new supabase browser client on every first render. + const [supabaseClient] = useState(() => createBrowserSupabaseClient()) + + return ( + + + + ) +} +``` + + + + +Wrap your `pages/_app.tsx` component with the `SessionContextProvider` component: + +```tsx lines=2,8 title=pages/_app.tsx +import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' +import { SessionContextProvider, Session } from '@supabase/auth-helpers-react' +import { useState } from 'react' + +function MyApp({ + Component, + pageProps, +}: AppProps<{ + initialSession: Session +}>) { + // Create a new supabase browser client on every first render. + const [supabaseClient] = useState(() => createBrowserSupabaseClient()) + + return ( + + + + ) +} +``` + + + + +You can now determine if a user is authenticated by checking that the `user` object returned by the `useUser()` hook is defined. + +## Usage with TypeScript + +You can pass types that were [generated with the Supabase CLI](/docs/reference/javascript/typescript-support#generating-types) to the Supabase Client to get enhanced type safety and auto completion: + +### Browser client + +Creating a new supabase client object: + +```tsx +import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' +import { Database } from '../database.types' + +const supabaseClient = createBrowserSupabaseClient() +``` + +Retrieving a supabase client object from the SessionContext: + +```tsx +import { useSupabaseClient } from '@supabase/auth-helpers-react' +import { Database } from '../database.types' + +const supabaseClient = useSupabaseClient() +``` + +### Server client + +```tsx +// Creating a new supabase server client object (e.g. in API route): +import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' +import type { NextApiRequest, NextApiResponse } from 'next' +import type { Database } from 'types_db' + +export default async (req: NextApiRequest, res: NextApiResponse) => { + const supabaseServerClient = createServerSupabaseClient({ + req, + res, + }) + const { + data: { user }, + } = await supabaseServerClient.auth.getUser() + + res.status(200).json({ name: user?.name ?? '' }) +} +``` + +## Client-side data fetching with RLS + +For [row level security](/docs/learn/auth-deep-dive/auth-row-level-security) to work properly when fetching data client-side, you need to make sure to use the `supabaseClient` from the `useSupabaseClient` hook and only run your query once the user is defined client-side in the `useUser()` hook: + +```jsx lines=10-17 +import { Auth } from '@supabase/auth-ui-react' +import { ThemeSupa } from '@supabase/auth-ui-shared' +import { useUser, useSupabaseClient } from '@supabase/auth-helpers-react' +import { useEffect, useState } from 'react' + +const LoginPage = () => { + const supabaseClient = useSupabaseClient() + const user = useUser() + const [data, setData] = useState() + + useEffect(() => { + async function loadData() { + const { data } = await supabaseClient.from('test').select('*') + setData(data) + } + // Only run query once user is logged in. + if (user) loadData() + }, [user]) + + if (!user) + return ( + + ) + + return ( + <> + +

user:

+
{JSON.stringify(user, null, 2)}
+

client-side data fetching with RLS

+
{JSON.stringify(data, null, 2)}
+ + ) +} + +export default LoginPage +``` + +## Server-side rendering (SSR) + +Create a server supabase client to retrieve the logged in user's session: + +```jsx title=pages/profile.js +import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' + +export default function Profile({ user }) { + return
Hello {user.name}
+} + +export const getServerSideProps = async (ctx) => { + // Create authenticated Supabase Client + const supabase = createServerSupabaseClient(ctx) + // Check if we have a session + const { + data: { session }, + } = await supabase.auth.getSession() + + if (!session) + return { + redirect: { + destination: '/', + permanent: false, + }, + } + + return { + props: { + initialSession: session, + user: session.user, + }, + } +} +``` + +## Server-side data fetching with RLS + +You can use the server supabase client to run [row level security](/docs/learn/auth-deep-dive/auth-row-level-security) authenticated queries server-side: + + + + +```jsx +import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' + +export default function ProtectedPage({ user, data }) { + return ( + <> +
Protected content for {user.email}
+
{JSON.stringify(data, null, 2)}
+
{JSON.stringify(user, null, 2)}
+ + ) +} + +export const getServerSideProps = async (ctx) => { + // Create authenticated Supabase Client + const supabase = createServerSupabaseClient(ctx) + // Check if we have a session + const { + data: { session }, + } = await supabase.auth.getSession() + + if (!session) + return { + redirect: { + destination: '/', + permanent: false, + }, + } + + // Run queries with RLS on the server + const { data } = await supabase.from('users').select('*') + + return { + props: { + initialSession: session, + user: session.user, + data: data ?? [], + }, + } +} +``` + +
+ + +```tsx +import { User, createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' +import { GetServerSidePropsContext } from 'next' + +export default function ProtectedPage({ user, data }: { user: User; data: any }) { + return ( + <> +
Protected content for {user.email}
+
{JSON.stringify(data, null, 2)}
+
{JSON.stringify(user, null, 2)}
+ + ) +} + +export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { + // Create authenticated Supabase Client + const supabase = createServerSupabaseClient(ctx) + // Check if we have a session + const { + data: { session }, + } = await supabase.auth.getSession() + + if (!session) + return { + redirect: { + destination: '/', + permanent: false, + }, + } + + // Run queries with RLS on the server + const { data } = await supabase.from('users').select('*') + + return { + props: { + initialSession: session, + user: session.user, + data: data ?? [], + }, + } +} +``` + +
+
+ +## Server-side data fetching to OAuth APIs using `provider token` {`#oauth-provider-token`} + +When using third-party auth providers, sessions are initiated with an additional `provider_token` field which is persisted in the auth cookie and can be accessed within the session object. The `provider_token` can be used to make API requests to the OAuth provider's API endpoints on behalf of the logged-in user. + + + + +```jsx +import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' + +export default function ProtectedPage({ user, allRepos }) { + return ( + <> +
Protected content for {user.email}
+

Data fetched with provider token:

+
{JSON.stringify(allRepos, null, 2)}
+

user:

+
{JSON.stringify(user, null, 2)}
+ + ) +} + +export const getServerSideProps = async (ctx) => { + // Create authenticated Supabase Client + const supabase = createServerSupabaseClient(ctx) + // Check if we have a session + const { + data: { session }, + } = await supabase.auth.getSession() + + if (!session) + return { + redirect: { + destination: '/', + permanent: false, + }, + } + + // Retrieve provider_token & logged in user's third-party id from metadata + const { provider_token, user } = session + const userId = user.user_metadata.user_name + + const allRepos = await ( + await fetch(`https://api.github.com/search/repositories?q=user:${userId}`, { + method: 'GET', + headers: { + Authorization: `token ${provider_token}`, + }, + }) + ).json() + + return { props: { user, allRepos } } +} +``` + +
+ + +```tsx +import { User, createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' +import { GetServerSidePropsContext } from 'next' + +export default function ProtectedPage({ user, allRepos }: { user: User; allRepos: any }) { + return ( + <> +
Protected content for {user.email}
+

Data fetched with provider token:

+
{JSON.stringify(allRepos, null, 2)}
+

user:

+
{JSON.stringify(user, null, 2)}
+ + ) +} + +export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { + // Create authenticated Supabase Client + const supabase = createServerSupabaseClient(ctx) + // Check if we have a session + const { + data: { session }, + } = await supabase.auth.getSession() + + if (!session) + return { + redirect: { + destination: '/', + permanent: false, + }, + } + + // Retrieve provider_token & logged in user's third-party id from metadata + const { provider_token, user } = session + const userId = user.user_metadata.user_name + + const allRepos = await ( + await fetch(`https://api.github.com/search/repositories?q=user:${userId}`, { + method: 'GET', + headers: { + Authorization: `token ${provider_token}`, + }, + }) + ).json() + + return { props: { user, allRepos } } +} +``` + +
+
+ +## Protecting API routes + +Create a server supabase client to retrieve the logged in user's session: + + + + +```jsx title=pages/api/protected-route.js +import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' + +const ProtectedRoute = async (req, res) => { + // Create authenticated Supabase Client + const supabase = createServerSupabaseClient({ req, res }) + // Check if we have a session + const { + data: { session }, + } = await supabase.auth.getSession() + + if (!session) + return res.status(401).json({ + error: 'not_authenticated', + description: 'The user does not have an active session or is not authenticated', + }) + + // Run queries with RLS on the server + const { data } = await supabase.from('test').select('*') + res.json(data) +} + +export default ProtectedRoute +``` + + + + +```tsx title=pages/api/protected-route.ts +import { NextApiHandler } from 'next' +import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' + +const ProtectedRoute: NextApiHandler = async (req, res) => { + // Create authenticated Supabase Client + const supabase = createServerSupabaseClient({ req, res }) + // Check if we have a session + const { + data: { session }, + } = await supabase.auth.getSession() + + if (!session) + return res.status(401).json({ + error: 'not_authenticated', + description: 'The user does not have an active session or is not authenticated', + }) + + // Run queries with RLS on the server + const { data } = await supabase.from('test').select('*') + res.json(data) +} + +export default ProtectedRoute +``` + + + + +## Auth with Next.js Middleware + +As an alternative to protecting individual pages you can use a [Next.js Middleware](https://nextjs.org/docs/middleware) to protect the entire directory or those that match the config object. In the following example, all requests to `/middleware-protected/*` will check whether a user is signed in, if successful the request will be forwarded to the destination route, otherwise the user will be redirected: + +```ts title=middleware.ts +import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs' +import { NextResponse } from 'next/server' +import type { NextRequest } from 'next/server' + +export async function middleware(req: NextRequest) { + // We need to create a response and hand it to the supabase client to be able to modify the response headers. + const res = NextResponse.next() + // Create authenticated Supabase Client. + const supabase = createMiddlewareSupabaseClient({ req, res }) + // Check if we have a session + const { + data: { session }, + } = await supabase.auth.getSession() + + // Check auth condition + if (session?.user.email?.endsWith('@gmail.com')) { + // Authentication successful, forward request to protected route. + return res + } + + // Auth condition not met, redirect to home page. + const redirectUrl = req.nextUrl.clone() + redirectUrl.pathname = '/' + redirectUrl.searchParams.set(`redirectedFrom`, req.nextUrl.pathname) + return NextResponse.redirect(redirectUrl) +} + +export const config = { + matcher: '/middleware-protected/:path*', +} +``` + +## Migration Guide + +### Migrating to v0.5.X + +To make these helpers more flexible as well as more maintainable and easier to upgrade for new versions of Next.js, we're stripping them down to the most useful part which is managing the cookies and giving you an authenticated supabase-js client in any environment (client, server, middleware/edge). + +Therefore we're marking the `withApiAuth`, `withPageAuth`, and `withMiddlewareAuth` higher order functions as deprecated and they will be removed in the next **minor** release (v0.6.X). + +Please follow the steps below to update your API routes, pages, and middleware handlers. Thanks! + +#### `withApiAuth` deprecated! + +Use `createServerSupabaseClient` within your `NextApiHandler`: + + + + +```tsx title=pages/api/protected-route.ts +import { withApiAuth } from '@supabase/auth-helpers-nextjs' + +export default withApiAuth(async function ProtectedRoute(req, res, supabase) { + // Run queries with RLS on the server + const { data } = await supabase.from('test').select('*') + res.json(data) +}) +``` + + + + +```tsx title=pages/api/protected-route.ts +import { NextApiHandler } from 'next' +import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' + +const ProtectedRoute: NextApiHandler = async (req, res) => { + // Create authenticated Supabase Client + const supabase = createServerSupabaseClient({ req, res }) + // Check if we have a session + const { + data: { session }, + } = await supabase.auth.getSession() + + if (!session) + return res.status(401).json({ + error: 'not_authenticated', + description: 'The user does not have an active session or is not authenticated', + }) + + // Run queries with RLS on the server + const { data } = await supabase.from('test').select('*') + res.json(data) +} + +export default ProtectedRoute +``` + + + + +#### `withPageAuth` deprecated! + +Use `createServerSupabaseClient` within `getServerSideProps`: + + + + +```tsx title=pages/profile.tsx +import { withPageAuth, User } from '@supabase/auth-helpers-nextjs' + +export default function Profile({ user }: { user: User }) { + return
{JSON.stringify(user, null, 2)}
+} + +export const getServerSideProps = withPageAuth({ redirectTo: '/' }) +``` + +
+ + +```tsx title=pages/profile.js +import { createServerSupabaseClient, User } from '@supabase/auth-helpers-nextjs' +import { GetServerSidePropsContext } from 'next' + +export default function Profile({ user }: { user: User }) { + return
{JSON.stringify(user, null, 2)}
+} + +export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { + // Create authenticated Supabase Client + const supabase = createServerSupabaseClient(ctx) + // Check if we have a session + const { + data: { session }, + } = await supabase.auth.getSession() + + if (!session) + return { + redirect: { + destination: '/', + permanent: false, + }, + } + + return { + props: { + initialSession: session, + user: session.user, + }, + } +} +``` + +
+
+ +#### `withMiddlewareAuth` deprecated! + + + + +```tsx title=middleware.ts +import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs' + +export const middleware = withMiddlewareAuth({ + redirectTo: '/', + authGuard: { + isPermitted: async (user) => { + return user.email?.endsWith('@gmail.com') ?? false + }, + redirectTo: '/insufficient-permissions', + }, +}) + +export const config = { + matcher: '/middleware-protected', +} +``` + + + + +```tsx title=middleware.ts +import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs' +import { NextResponse } from 'next/server' +import type { NextRequest } from 'next/server' + +export async function middleware(req: NextRequest) { + // We need to create a response and hand it to the supabase client to be able to modify the response headers. + const res = NextResponse.next() + // Create authenticated Supabase Client. + const supabase = createMiddlewareSupabaseClient({ req, res }) + // Check if we have a session + const { + data: { session }, + } = await supabase.auth.getSession() + + // Check auth condition + if (session?.user.email?.endsWith('@gmail.com')) { + // Authentication successful, forward request to protected route. + return res + } + + // Auth condition not met, redirect to home page. + const redirectUrl = req.nextUrl.clone() + redirectUrl.pathname = '/' + redirectUrl.searchParams.set(`redirectedFrom`, req.nextUrl.pathname) + return NextResponse.redirect(redirectUrl) +} + +export const config = { + matcher: '/middleware-protected', +} +``` + + + + +### Migrating to v0.4.X and supabase-js v2 + +With the update to `supabase-js` v2 the `auth` API routes are no longer required, therefore you can go ahead and delete your `auth` directory under the `/pages/api/` directory. Please refer to the [v2 migration guide](/docs/reference/javascript/v1/upgrade-guide) for the full set of changes within supabase-js. + +The `/api/auth/logout` API route has been removed, please use the `signout` method instead: + +```jsx + +``` + +The `supabaseClient` and `supabaseServerClient` have been removed in favor of the `createBrowserSupabaseClient` and `createServerSupabaseClient` methods. This allows you to provide the CLI-generated types to the client: + +```tsx +// client-side +import type { Database } from 'types_db' +const [supabaseClient] = useState(() => createBrowserSupabaseClient()) + +// server-side API route +import type { NextApiRequest, NextApiResponse } from 'next' +import type { Database } from 'types_db' + +export default async (req: NextApiRequest, res: NextApiResponse) => { + const supabaseServerClient = createServerSupabaseClient({ + req, + res, + }) + const { + data: { user }, + } = await supabaseServerClient.auth.getUser() + + res.status(200).json({ name: user?.name ?? '' }) +} +``` + +- The `UserProvider` has been replaced by the `SessionContextProvider`. Make sure to wrap your `pages/_app.js` componenent with the `SessionContextProvider`. Then, throughout your application you can use the `useSessionContext` hook to get the `session` and the `useSupabaseClient` hook to get an authenticated `supabaseClient`. +- The `useUser` hook now returns the `user` object or `null`. +- Usage with TypeScript: You can pass types that were [generated with the Supabase CLI](/docs/reference/javascript/typescript-support#generating-types) to the Supabase Client to get enhanced type safety and auto completion: + +Creating a new supabase client object: + +```tsx +import { Database } from '../database.types' + +const [supabaseClient] = useState(() => createBrowserSupabaseClient()) +``` + +Retrieving a supabase client object from the SessionContext: + +```tsx +import { useSupabaseClient } from '@supabase/auth-helpers-react' +import { Database } from '../database.types' + +const supabaseClient = useSupabaseClient() +``` + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/auth/auth-helpers/nextjs-server-components.mdx b/apps/docs/pages/guides/auth/auth-helpers/nextjs-server-components.mdx deleted file mode 100644 index 40f28959556..00000000000 --- a/apps/docs/pages/guides/auth/auth-helpers/nextjs-server-components.mdx +++ /dev/null @@ -1,514 +0,0 @@ -import Layout from '~/layouts/DefaultGuideLayout' - -export const meta = { - id: 'nextjs-server-components', - title: 'Supabase Auth with Next.js app directory', - description: - 'Authentication helpers for creating an authenticated Supabase client in Next.js 13 app directory Server Components and Route Handlers.', - sidebar_label: 'Next.js (app)', -} - -The Next.js Auth Helpers package configures Supabase Auth to store the user's session in a cookie, rather than `localStorage`. This makes the users's session available server-side - in Server Components and Route Handlers - and is automatically sent along with any requests to Supabase. - -> Note: If you are using the `pages` directory, check out [Auth Helpers in Next.js](/docs/guides/auth/auth-helpers/nextjs). - -
- -
- -> To learn more about Supabase and the Next.js 13 app directory, check out [this playlist](https://youtube.com/playlist?list=PL5S4mPUpp4OtwG-qCxm8gA_hjaBq0OPdz). - -## Install the Next.js helper library - - - - - -```sh -npm install @supabase/auth-helpers-nextjs -``` - - - - -```sh -yarn add @supabase/auth-helpers-nextjs -``` - - - - -## Set up environment variables - -Retrieve your project's URL and anon key from your [API settings](https://app.supabase.com/project/_/settings/api) in the dashboard, and create a `.env.local` file with the following environment variables: - -```bash title=".env.local" -NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL -NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY -``` - -## Configure Middleware - - - - -Middleware runs immediately before each route in rendered. Next.js only provides read access to headers and cookies in Server Components and Route Handlers, however, Supabase needs to be able to set cookies and headers to refresh expired access tokens. Therefore, you must call the `getSession` function in `middleware.js` in order to use a Supabase client in Server Components or Route Handlers. - -Create a new `middleware.js` file in the root of your project and populate with the following: - -```jsx title="middleware.js" -import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { NextResponse } from 'next/server' - -export async function middleware(req) { - const res = NextResponse.next() - const supabase = createMiddlewareSupabaseClient({ req, res }) - await supabase.auth.getSession() - return res -} -``` - - - - - -Middleware runs immediately before each route in rendered. Next.js only provides read access to headers and cookies in Server Components and Route Handlers, however, Supabase needs to be able to set cookies and headers to refresh expired access tokens. Therefore, you must call the `getSession` function in `middleware.ts` in order to use a Supabase client in Server Components or Route Handlers. - -Create a new `middleware.ts` file in the root of your project and populate with the following: - -```tsx title="middleware.ts" -import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { NextResponse } from 'next/server' - -import type { NextRequest } from 'next/server' -import type { Database } from '@/lib/database.types' - -export async function middleware(req: NextRequest) { - const res = NextResponse.next() - const supabase = createMiddlewareSupabaseClient({ req, res }) - await supabase.auth.getSession() - return res -} -``` - -> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createMiddlewareSupabaseClient` to add type support to the Supabase client. - - - - -## Supabase Provider - -All Client Components need to share a single instance of the Supabase client. We can wrap our application in a `` and use React Context to create a global Supabase instance. - - - - -Create a new file at `/app/supabase-provider.jsx` and populate with the following: - -```jsx title="app/supabase-provider.jsx" -'use client' - -import { createContext, useContext, useEffect, useState } from 'react' -import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { useRouter } from 'next/navigation' - -const Context = createContext(undefined) - -export default function SupabaseProvider({ children, session }) { - const [supabase] = useState(() => createBrowserSupabaseClient()) - const router = useRouter() - - useEffect(() => { - const { - data: { subscription }, - } = supabase.auth.onAuthStateChange(() => { - router.refresh() - }) - - return () => { - subscription.unsubscribe() - } - }, [router, supabase]) - - return ( - - <>{children} - - ) -} - -export const useSupabase = () => { - const context = useContext(Context) - - if (context === undefined) { - throw new Error('useSupabase must be used inside SupabaseProvider') - } - - return context -} -``` - - - - - -Create a new file at `/app/supabase-provider.tsx` and populate with the following: - -```tsx title="app/supabase-provider.tsx" -'use client' - -import { createContext, useContext, useEffect, useState } from 'react' -import { Session, createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { useRouter } from 'next/navigation' - -import type { SupabaseClient } from '@supabase/auth-helpers-nextjs' -import type { Database } from '@/lib/database.types' - -type MaybeSession = Session | null - -type SupabaseContext = { - supabase: SupabaseClient - session: MaybeSession -} - -const Context = createContext(undefined) - -export default function SupabaseProvider({ - children, - session, -}: { - children: React.ReactNode - session: MaybeSession -}) { - const [supabase] = useState(() => createBrowserSupabaseClient()) - const router = useRouter() - - useEffect(() => { - const { - data: { subscription }, - } = supabase.auth.onAuthStateChange(() => { - router.refresh() - }) - - return () => { - subscription.unsubscribe() - } - }, [router, supabase]) - - return ( - - <>{children} - - ) -} - -export const useSupabase = () => { - const context = useContext(Context) - - if (context === undefined) { - throw new Error('useSupabase must be used inside SupabaseProvider') - } - - return context -} -``` - -> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createBrowserSupabaseClient` to add type support to the Supabase client. - - - - - - - -Modify `layout.jsx` to wrap the application with the `` component: - -```jsx title="app/layout.jsx" -import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { headers, cookies } from 'next/headers' -import './globals.css' -import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { headers, cookies } from 'next/headers' -import SupabaseProvider from './supabase-provider' - -export const metadata = { - title: 'Create Next App', - description: 'Generated by create next app', -} - -export default async function RootLayout({ children }) { - const supabase = createServerComponentClient({ headers, cookies }) - - const { - data: { session }, - } = await supabase.auth.getSession() - - return ( - - - {children} - - - ) -} -``` - - - - - -Modify `layout.tsx` to wrap the application with the `` component: - -```tsx title="app/layout.tsx" -import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { headers, cookies } from 'next/headers' -import './globals.css' -import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { headers, cookies } from 'next/headers' -import SupabaseProvider from './supabase-provider' - -export const metadata = { - title: 'Create Next App', - description: 'Generated by create next app', -} - -export default async function RootLayout({ children }: { children: React.ReactNode }) { - const supabase = createServerComponentSupabaseClient({ headers, cookies }) - - const { - data: { session }, - } = await supabase.auth.getSession() - - return ( - - - {children} - - - ) -} -``` - - - - -Now any of our Client Components can use the `useSupabase` hook to ensure they are using the same instance of a Supabase client. - -## Creating a Supabase Client - -### Client Components - -While Server Components are great for data fetching, we still need to use Supabase client-side for [authentication](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/login.tsx) and [realtime subscriptions](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/realtime-posts.tsx). - -As mentioned above, it is important that all Client Components share a single instance of the Supabase client. We can use the `useSupabase` hook we created above to ensure this is the case. - - - - -```jsx title="app/new-post.jsx" -'use client' - -import { useState } from 'react' -import { useSupabase } from './supabase-provider' - -export default function NewPost() { - const [content, setContent] = useState('') - const { supabase } = useSupabase() - - const handleSave = async () => { - const { data } = await supabase.from('posts').insert({ content }).select() - } - - return ( - <> - setContent(e.target.value)} value={content} /> - - - ) -} -``` - - - - - -```jsx title="app/new-post.tsx" -'use client' - -import { useState } from 'react' -import { useSupabase } from './supabase-provider' - -export default function NewPost() { - const [content, setContent] = useState('') - const { supabase } = useSupabase() - - const handleSave = async () => { - const { data } = await supabase.from('posts').insert({ content }).select() - } - - return ( - <> - setContent(e.target.value)} value={content} /> - - - ) -} -``` - - - - -> check out [this example](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/supabase-provider.tsx) for making the user's session available to all Client Components. - -### Server Components - -In order to use Supabase in Server Components, you need to have implemented the `middleware.ts` steps above 👆 - - - - -```jsx title="app/page.jsx" -import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { headers, cookies } from 'next/headers' - -// do not cache this page -export const revalidate = 0 - -export default async function ServerComponent() { - const supabase = createServerComponentSupabaseClient({ - headers, - cookies, - }) - const { data } = await supabase.from('posts').select('*') - - return
{JSON.stringify(data, null, 2)}
-} -``` - -
- - - -```tsx title="app/page.tsx" -import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { headers, cookies } from 'next/headers' - -import type { Database } from '@/lib/database.types' - -// do not cache this page -export const revalidate = 0 - -export default async function ServerComponent() { - const supabase = createServerComponentSupabaseClient({ - headers, - cookies, - }) - const { data } = await supabase.from('posts').select('*') - - return
{JSON.stringify(data, null, 2)}
-} -``` - -
-
- -> check out [this example](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/page.tsx) for redirecting unauthenticated users - protected pages. - -### Route Handlers - -In order to use Supabase in Route Handlers, you need to have implemented the `middleware.ts` steps above 👆 - - - - -```jsx title="app/api/posts/route.jsx" -import { createRouteHandlerSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { NextResponse } from 'next/server' -import { headers, cookies } from 'next/headers' - -// do not cache this page -export const revalidate = 0 - -export async function GET() { - const supabase = createRouteHandlerSupabaseClient({ - headers, - cookies, - }) - const { data } = await supabase.from('posts').select('*') - return NextResponse.json(data) -} -``` - - - - - -```tsx title="app/api/posts/route.tsx" -import { createRouteHandlerSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { NextResponse } from 'next/server' -import { headers, cookies } from 'next/headers' - -import type { Database } from '@/lib/database.types' - -// do not cache this page -export const revalidate = 0 - -export async function GET() { - const supabase = createRouteHandlerSupabaseClient({ - headers, - cookies, - }) - const { data } = await supabase.from('posts').select('*') - - return NextResponse.json(data) -} -``` - - - - -> Check out [this repo](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs) for a full example including [authentication](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/login.tsx), [realtime](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/realtime-posts.tsx) and [protected pages](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/page.tsx). - -export const Page = ({ children }) => - -export default Page diff --git a/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx b/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx index 2a5cb8a96f4..86edb6a6c61 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/nextjs.mdx @@ -2,14 +2,19 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'nextjs', - title: 'Supabase Auth with Next.js', - description: 'Authentication helpers for Next.js API routes, middleware, and SSR.', - sidebar_label: 'Next.js (pages)', + title: 'Supabase Auth with the Next.js App Router', + description: + 'Authentication and Authorization helpers for creating an authenticated Supabase client with the Next.js 13 App Router.', + sidebar_label: 'Next.js', } -This submodule provides convenience helpers for implementing user authentication in Next.js applications. +The [Next.js Auth Helpers package](https://github.com/supabase/auth-helpers) configures Supabase Auth to store the user's `session` in a `cookie`, rather than `localStorage`. This makes it available across the client and server of the App Router - [Client Components](/docs/guides/auth/auth-helpers/nextjs#client-components), [Server Components](/docs/guides/auth/auth-helpers/nextjs#server-components), [Server Actions](/docs/guides/auth/auth-helpers/nextjs#server-actions), [Route Handlers](/docs/guides/auth/auth-helpers/nextjs#route-handlers) and [Middleware](/docs/guides/auth/auth-helpers/nextjs#middleware). The `session` is automatically sent along with any requests to Supabase. -## Install the Next.js helper library +> Note: If you are using the `pages` directory, check out [Auth Helpers in Next.js Pages Directory](/docs/guides/auth/auth-helpers/nextjs-pages). + +## Configuration + +### Install Next.js Auth Helpers library + ```sh npm install @supabase/auth-helpers-nextjs ``` -This library supports the following tooling versions: - -- Node.js: `^10.13.0 || >=12.0.0` -- Next.js: `>=10` - -> Note: As of [Next.js 13.4](https://nextjs.org/blog/next-13-4), the `app` directory and Server Components have reached stable status. Check out our guide on using [Auth Helpers with Next.js Server Components](/docs/guides/auth/auth-helpers/nextjs-server-components). - -Additionally, install the **React Auth Helpers** for components and hooks that can be used across all React-based frameworks. - -```sh -npm install @supabase/auth-helpers-react -``` - @@ -43,32 +36,21 @@ npm install @supabase/auth-helpers-react yarn add @supabase/auth-helpers-nextjs ``` -This library supports the following tooling versions: - -- Node.js: `^10.13.0 || >=12.0.0` -- Next.js: `>=10` - -> Note: Next.js 13 is stable, however, the new `app` directory and Server Components are still in beta. Check out our experimental guide on [using Auth Helpers with Next.js Server Components](/docs/guides/auth/auth-helpers/nextjs-server-components). - -Additionally, install the **React Auth Helpers** for components and hooks that can be used across all React-based frameworks. - -```sh -yarn add @supabase/auth-helpers-react -``` - -## Set up environment variables +### Declare Environment Variables -Retrieve your project URL and anon key in your project's [API settings](https://app.supabase.com/project/_/settings/api) in the Dashboard to set up the following environment variables. For local development you can set them in a `.env.local` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/nextjs/.env.local.example). +Retrieve your project's URL and anon key from your [API settings](https://app.supabase.com/project/_/settings/api), and create a `.env.local` file with the following environment variables: -```bash title=.env.local -NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL -NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY +```bash title=".env.local" +NEXT_PUBLIC_SUPABASE_URL=your-supabase-url +NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key ``` -## Basic Setup +### Refresh session with Middleware + +[Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware) runs immediately before each route in rendered. Next.js only provides read access to cookies in Server Components, therefore, Middleware is used to refresh the user's session before loading Server Component routes. -Wrap your `pages/_app.js` component with the `SessionContextProvider` component: +Create a new `middleware.js` file in the root of your project and populate with the following: -```jsx title=pages/_app.js -import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { SessionContextProvider } from '@supabase/auth-helpers-react' -import { useState } from 'react' +```jsx title="middleware.js" +import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs' +import { NextResponse } from 'next/server' -function MyApp({ Component, pageProps }) { - // Create a new supabase browser client on every first render. - const [supabaseClient] = useState(() => createBrowserSupabaseClient()) - - return ( - - - - ) +export async function middleware(req) { + const res = NextResponse.next() + const supabase = createMiddlewareClient({ req, res }) + await supabase.auth.getSession() + return res } ``` + -Wrap your `pages/_app.tsx` component with the `SessionContextProvider` component: +Create a new `middleware.ts` file in the root of your project and populate with the following: -```tsx lines=2,8 title=pages/_app.tsx -import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { SessionContextProvider, Session } from '@supabase/auth-helpers-react' -import { useState } from 'react' +```tsx title="middleware.ts" +import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs' +import { NextResponse } from 'next/server' -function MyApp({ - Component, - pageProps, -}: AppProps<{ - initialSession: Session -}>) { - // Create a new supabase browser client on every first render. - const [supabaseClient] = useState(() => createBrowserSupabaseClient()) +import type { NextRequest } from 'next/server' +import type { Database } from '@/lib/database.types' - return ( - - - - ) +export async function middleware(req: NextRequest) { + const res = NextResponse.next() + const supabase = createMiddlewareClient({ req, res }) + await supabase.auth.getSession() + return res } ``` +> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createMiddlewareClient` to add type support to the Supabase client. + -You can now determine if a user is authenticated by checking that the `user` object returned by the `useUser()` hook is defined. +> The `getSession` function must be called for any Server Component routes that use a Supabase client. -## Usage with TypeScript +### Code Exchange Route -You can pass types that were [generated with the Supabase CLI](/docs/reference/javascript/typescript-support#generating-types) to the Supabase Client to get enhanced type safety and auto completion: +The `Code Exchange` route is required for the [server-side auth flow](https://supabase.com/docs/guides/auth/server-side-rendering) implemented by the Next.js Auth Helpers. It exchanges an auth `code` for the user's `session`, which is set as a cookie for future requests made to Supabase. -### Browser client + + -Creating a new supabase client object: +Create a new file at `app/auth/callback/route.js` and populate with the following: -```tsx -import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { Database } from '../database.types' +```jsx title="app/auth/callback.route.js" +import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs' +import { cookies } from 'next/headers' +import { NextResponse } from 'next/server' -const supabaseClient = createBrowserSupabaseClient() -``` +export async function GET(request) { + const requestUrl = new URL(request.url) + const code = requestUrl.searchParams.get('code') -Retrieving a supabase client object from the SessionContext: + if (code) { + const supabase = createRouteHandlerClient({ cookies }) + await supabase.auth.exchangeCodeForSession(code) + } -```tsx -import { useSupabaseClient } from '@supabase/auth-helpers-react' -import { Database } from '../database.types' - -const supabaseClient = useSupabaseClient() -``` - -### Server client - -```tsx -// Creating a new supabase server client object (e.g. in API route): -import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' -import type { NextApiRequest, NextApiResponse } from 'next' -import type { Database } from 'types_db' - -export default async (req: NextApiRequest, res: NextApiResponse) => { - const supabaseServerClient = createServerSupabaseClient({ - req, - res, - }) - const { - data: { user }, - } = await supabaseServerClient.auth.getUser() - - res.status(200).json({ name: user?.name ?? '' }) + // URL to redirect to after sign in process completes + return NextResponse.redirect(requestUrl.origin) } ``` -## Client-side data fetching with RLS + -For [row level security](/docs/learn/auth-deep-dive/auth-row-level-security) to work properly when fetching data client-side, you need to make sure to use the `supabaseClient` from the `useSupabaseClient` hook and only run your query once the user is defined client-side in the `useUser()` hook: + -```jsx lines=10-17 -import { Auth } from '@supabase/auth-ui-react' -import { ThemeSupa } from '@supabase/auth-ui-shared' -import { useUser, useSupabaseClient } from '@supabase/auth-helpers-react' +Create a new file at `app/auth/callback/route.ts` and populate with the following: + +```tsx title="app/auth/callback.route.ts" +import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs' +import { cookies } from 'next/headers' +import { NextResponse } from 'next/server' + +import type { NextRequest } from 'next/server' +import type { Database } from '@/lib/database.types' + +export async function GET(request: NextRequest) { + const requestUrl = new URL(request.url) + const code = requestUrl.searchParams.get('code') + + if (code) { + const supabase = createRouteHandlerClient({ cookies }) + await supabase.auth.exchangeCodeForSession(code) + } + + // URL to redirect to after sign in process completes + return NextResponse.redirect(requestUrl.origin) +} +``` + +> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createRouteHandlerClient` to add type support to the Supabase client. + + + + +## Authentication + +Authentication can be initiated [client](/docs/guides/auth/auth-helpers/nextjs#client-side) or [server-side](/docs/guides/auth/auth-helpers/nextjs#server-side). All of the [supabase-js authentication strategies](http://localhost:3001/docs/reference/javascript/auth-api) are supported with the Auth Helpers client. + +> Note: The authentication flow requires the [Code Exchange Route](/docs/guides/auth/auth-helpers/nextjs#code-exchange-route) to exchange a `code` for the user's `session`. + +### Client-side + +Client Components can be used to trigger the authentication process from event handlers. + + + + +```jsx title="app/login.js" +'use client' + +import { createClientComponentClient } from '@supabase/auth-helpers-nextjs' +import { useRouter } from 'next/navigation' +import { useState } from 'react' + +export default function Login() { + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const router = useRouter() + const supabase = createClientComponentClient() + + const handleSignUp = async () => { + await supabase.auth.signUp({ + email, + password, + options: { + emailRedirectTo: `${location.origin}/auth/callback`, + }, + }) + router.refresh() + } + + const handleSignIn = async () => { + await supabase.auth.signInWithPassword({ + email, + password, + }) + router.refresh() + } + + const handleSignOut = async () => { + await supabase.auth.signOut() + router.refresh() + } + + return ( + <> + setEmail(e.target.value)} value={email} /> + setPassword(e.target.value)} + value={password} + /> + + + + + ) +} +``` + + + + + +```tsx title="app/login.ts" +'use client' + +import { createClientComponentClient } from '@supabase/auth-helpers-nextjs' +import { useRouter } from 'next/navigation' +import { useState } from 'react' + +import type { Database } from '@/lib/database.types' + +export default function Login() { + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const router = useRouter() + const supabase = createClientComponentClient() + + const handleSignUp = async () => { + await supabase.auth.signUp({ + email, + password, + options: { + emailRedirectTo: `${location.origin}/auth/callback`, + }, + }) + router.refresh() + } + + const handleSignIn = async () => { + await supabase.auth.signInWithPassword({ + email, + password, + }) + router.refresh() + } + + const handleSignOut = async () => { + await supabase.auth.signOut() + router.refresh() + } + + return ( + <> + setEmail(e.target.value)} value={email} /> + setPassword(e.target.value)} + value={password} + /> + + + + + ) +} +``` + +> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createClientComponentClient` to add type support to the Supabase client. + + + + +### Server-side + +The combination of [Server Components](https://nextjs.org/docs/getting-started/react-essentials#server-components) and [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions) can be used to trigger the authentication process from form submissions. + +> Note: [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions) are currently in Alpha and likely to change. We recommend [triggering the authentication flow client-side](/docs/guides/auth/auth-helpers/nextjs#client-side) for production applications. + + + + +```jsx title="app/login.js" +import { createServerActionClient } from '@supabase/auth-helpers-nextjs' +import { revalidatePath } from 'next/cache' +import { cookies } from 'next/headers' + +export default async function Login() { + const handleSignUp = async (formData) => { + 'use server' + const email = formData.get('email') + const password = formData.get('password') + + const supabase = createServerActionClient({ cookies }) + await supabase.auth.signUp({ + email, + password, + options: { + emailRedirectTo: 'http://localhost:3000/auth/callback', + }, + }) + + revalidatePath('/') + } + + const handleSignIn = async (formData) => { + 'use server' + const email = formData.get('email') + const password = formData.get('password') + + const supabase = createServerActionClient({ cookies }) + await supabase.auth.signInWithPassword({ + email, + password, + }) + + revalidatePath('/') + } + + const handleSignOut = async () => { + 'use server' + const supabase = createServerActionClient({ cookies }) + await supabase.auth.signOut() + revalidatePath('/') + } + + return ( +
+ + + + + +
+ ) +} +``` + +
+ + + +```tsx title="app/login.ts" +import { createServerActionClient } from '@supabase/auth-helpers-nextjs' +import { revalidatePath } from 'next/cache' +import { cookies } from 'next/headers' + +import type { Database } from '@/lib/database.types' + +export default async function Login() { + const handleSignUp = async (formData: FormData) => { + 'use server' + const email = String(formData.get('email')) + const password = String(formData.get('password')) + + const supabase = createServerActionClient({ cookies }) + await supabase.auth.signUp({ + email, + password, + options: { + emailRedirectTo: 'http://localhost:3000/auth/callback', + }, + }) + + revalidatePath('/') + } + + const handleSignIn = async (formData: FormData) => { + 'use server' + const email = String(formData.get('email')) + const password = String(formData.get('password')) + + const supabase = createServerActionClient({ cookies }) + await supabase.auth.signInWithPassword({ + email, + password, + }) + + revalidatePath('/') + } + + const handleSignOut = async () => { + 'use server' + const supabase = createServerActionClient({ cookies }) + await supabase.auth.signOut() + revalidatePath('/') + } + + return ( +
+ + + + + +
+ ) +} +``` + +> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createServerActionClient` to add type support to the Supabase client. + +
+
+ +## Creating a Supabase Client + +### Client Component + +[Client Components](https://nextjs.org/docs/getting-started/react-essentials#client-components) allow the use of client-side hooks - such as `useEffect` and `useState`. They can be used to request data from Supabase client-side, and [subscribe to realtime events](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/realtime-posts.tsx). + + + + +```jsx title="app/client/page.jsx" +'use client' + +import { createClientComponentClient } from '@supabase/auth-helpers-nextjs' import { useEffect, useState } from 'react' -const LoginPage = () => { - const supabaseClient = useSupabaseClient() - const user = useUser() - const [data, setData] = useState() +export default function Home() { + const [todos, setTodos] = useState() + const supabase = createClientComponentClient() useEffect(() => { - async function loadData() { - const { data } = await supabaseClient.from('test').select('*') - setData(data) - } - // Only run query once user is logged in. - if (user) loadData() - }, [user]) - - if (!user) - return ( - - ) - - return ( - <> - -

user:

-
{JSON.stringify(user, null, 2)}
-

client-side data fetching with RLS

-
{JSON.stringify(data, null, 2)}
- - ) -} - -export default LoginPage -``` - -## Server-side rendering (SSR) - -Create a server supabase client to retrieve the logged in user's session: - -```jsx title=pages/profile.js -import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' - -export default function Profile({ user }) { - return
Hello {user.name}
-} - -export const getServerSideProps = async (ctx) => { - // Create authenticated Supabase Client - const supabase = createServerSupabaseClient(ctx) - // Check if we have a session - const { - data: { session }, - } = await supabase.auth.getSession() - - if (!session) - return { - redirect: { - destination: '/', - permanent: false, - }, + const getData = async () => { + const { data } = await supabase.from('todos').select() + setTodos(data) } - return { - props: { - initialSession: session, - user: session.user, - }, - } + getData() + }, []) + + return todos ?
{JSON.stringify(todos, null, 2)}
:

Loading todos...

} ``` -## Server-side data fetching with RLS +
-You can use the server supabase client to run [row level security](/docs/learn/auth-deep-dive/auth-row-level-security) authenticated queries server-side: + + +```jsx title="app/new-post.tsx" +"use client"; + +import { createClientComponentClient } from "@supabase/auth-helpers-nextjs"; +import { useEffect, useState } from "react"; + +import type { Database } from '@/lib/database.types' + +type Todo = Database["public"]["Tables"]["todos"]["Row"]; + +export default function Home() { + const [todos, setTodos] = useState(null); + const supabase = createClientComponentClient(); + + useEffect(() => { + const getData = async () => { + const { data } = await supabase.from("todos").select(); + setTodos(data); + }; + + getData(); + }, []); + + return todos ? ( +
{JSON.stringify(todos, null, 2)}
+ ) : ( +

Loading todos...

+ ); +} +``` + +> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createClientComponentClient` to add type support to the Supabase client. + +
+
+ +> check out [this repo](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/page.tsx) for more examples, including realtime subscriptions. + +### Server Component + +[Server Components](https://nextjs.org/docs/getting-started/react-essentials#server-components) allow for asynchronous data to be fetched server-side. + +> Note: In order to use Supabase in Server Components, you need to have implemented the [Middleware](/docs/guides/auth/auth-helpers/nextjs#refresh-session-with-middleware) steps above. -```jsx -import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' +```jsx title="app/page.jsx" +import { cookies } from 'next/headers' +import { createServerComponentClient } from '@supabase/auth-helpers-nextjs' -export default function ProtectedPage({ user, data }) { - return ( - <> -
Protected content for {user.email}
-
{JSON.stringify(data, null, 2)}
-
{JSON.stringify(user, null, 2)}
- - ) -} - -export const getServerSideProps = async (ctx) => { - // Create authenticated Supabase Client - const supabase = createServerSupabaseClient(ctx) - // Check if we have a session - const { - data: { session }, - } = await supabase.auth.getSession() - - if (!session) - return { - redirect: { - destination: '/', - permanent: false, - }, - } - - // Run queries with RLS on the server - const { data } = await supabase.from('users').select('*') - - return { - props: { - initialSession: session, - user: session.user, - data: data ?? [], - }, - } +export default async function Home() { + const supabase = createServerComponentClient({ cookies }) + const { data } = await supabase.from('todos').select() + return
{JSON.stringify(data, null, 2)}
} ```
+ -```tsx -import { User, createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { GetServerSidePropsContext } from 'next' +```tsx title="app/page.tsx" +import { cookies } from 'next/headers' +import { createServerComponentClient } from '@supabase/auth-helpers-nextjs' -export default function ProtectedPage({ user, data }: { user: User; data: any }) { - return ( - <> -
Protected content for {user.email}
-
{JSON.stringify(data, null, 2)}
-
{JSON.stringify(user, null, 2)}
- - ) -} +import type { Database } from '@/lib/database.types' -export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { - // Create authenticated Supabase Client - const supabase = createServerSupabaseClient(ctx) - // Check if we have a session - const { - data: { session }, - } = await supabase.auth.getSession() - - if (!session) - return { - redirect: { - destination: '/', - permanent: false, - }, - } - - // Run queries with RLS on the server - const { data } = await supabase.from('users').select('*') - - return { - props: { - initialSession: session, - user: session.user, - data: data ?? [], - }, - } +export default async function ServerComponent() { + const supabase = createServerComponentClient({ cookies }) + const { data } = await supabase.from('todos').select() + return
{JSON.stringify(data, null, 2)}
} ``` +> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createServerComponentClient` to add type support to the Supabase client. +
-## Server-side data fetching to OAuth APIs using `provider token` {`#oauth-provider-token`} +> check out [this repo](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/page.tsx) for more examples, including redirecting unauthenticated users - protected pages. -When using third-party auth providers, sessions are initiated with an additional `provider_token` field which is persisted in the auth cookie and can be accessed within the session object. The `provider_token` can be used to make API requests to the OAuth provider's API endpoints on behalf of the logged-in user. +### Server Action + +[Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions) allow mutations to be performed server-side. + +> Note: Server Actions are currently in `alpha` so may change without notice. -```jsx -import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' +```jsx title="app/new-post.jsx" +import { cookies } from 'next/headers' +import { createServerActionClient } from '@supabase/auth-helpers-nextjs' +import { revalidatePath } from 'next/cache' + +export default async function NewTodo() { + const addTodo = async (formData) => { + 'use server' + + const title = formData.get('title') + const supabase = createServerActionClient({ cookies }) + await supabase.from('todos').insert({ title }) + revalidatePath('/') + } -export default function ProtectedPage({ user, allRepos }) { return ( - <> -
Protected content for {user.email}
-

Data fetched with provider token:

-
{JSON.stringify(allRepos, null, 2)}
-

user:

-
{JSON.stringify(user, null, 2)}
- +
+ +
) } - -export const getServerSideProps = async (ctx) => { - // Create authenticated Supabase Client - const supabase = createServerSupabaseClient(ctx) - // Check if we have a session - const { - data: { session }, - } = await supabase.auth.getSession() - - if (!session) - return { - redirect: { - destination: '/', - permanent: false, - }, - } - - // Retrieve provider_token & logged in user's third-party id from metadata - const { provider_token, user } = session - const userId = user.user_metadata.user_name - - const allRepos = await ( - await fetch(`https://api.github.com/search/repositories?q=user:${userId}`, { - method: 'GET', - headers: { - Authorization: `token ${provider_token}`, - }, - }) - ).json() - - return { props: { user, allRepos } } -} ```
+ -```tsx -import { User, createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' -import { GetServerSidePropsContext } from 'next' +```tsx title="app/new-post.tsx" +import { cookies } from 'next/headers' +import { createServerActionClient } from '@supabase/auth-helpers-nextjs' +import { revalidatePath } from 'next/cache' + +import type { Database } from '@/lib/database.types' + +export default async function NewTodo() { + const addTodo = async (formData: FormData) => { + 'use server' + + const title = formData.get('title') + const supabase = createServerActionClient({ cookies }) + await supabase.from('todos').insert({ title }) + revalidatePath('/') + } -export default function ProtectedPage({ user, allRepos }: { user: User; allRepos: any }) { return ( - <> -
Protected content for {user.email}
-

Data fetched with provider token:

-
{JSON.stringify(allRepos, null, 2)}
-

user:

-
{JSON.stringify(user, null, 2)}
- +
+ +
) } - -export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { - // Create authenticated Supabase Client - const supabase = createServerSupabaseClient(ctx) - // Check if we have a session - const { - data: { session }, - } = await supabase.auth.getSession() - - if (!session) - return { - redirect: { - destination: '/', - permanent: false, - }, - } - - // Retrieve provider_token & logged in user's third-party id from metadata - const { provider_token, user } = session - const userId = user.user_metadata.user_name - - const allRepos = await ( - await fetch(`https://api.github.com/search/repositories?q=user:${userId}`, { - method: 'GET', - headers: { - Authorization: `token ${provider_token}`, - }, - }) - ).json() - - return { props: { user, allRepos } } -} ``` +> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createServerActionClient` to add type support to the Supabase client. +
-## Protecting API routes +### Route Handler -Create a server supabase client to retrieve the logged in user's session: +[Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/router-handlers) replace API Routes and allow for logic to be performed server-side. They can respond to `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, and `OPTIONS` requests. -```jsx title=pages/api/protected-route.js -import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' - -const ProtectedRoute = async (req, res) => { - // Create authenticated Supabase Client - const supabase = createServerSupabaseClient({ req, res }) - // Check if we have a session - const { - data: { session }, - } = await supabase.auth.getSession() - - if (!session) - return res.status(401).json({ - error: 'not_authenticated', - description: 'The user does not have an active session or is not authenticated', - }) - - // Run queries with RLS on the server - const { data } = await supabase.from('test').select('*') - res.json(data) -} - -export default ProtectedRoute -``` - - - - -```tsx title=pages/api/protected-route.ts -import { NextApiHandler } from 'next' -import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' - -const ProtectedRoute: NextApiHandler = async (req, res) => { - // Create authenticated Supabase Client - const supabase = createServerSupabaseClient({ req, res }) - // Check if we have a session - const { - data: { session }, - } = await supabase.auth.getSession() - - if (!session) - return res.status(401).json({ - error: 'not_authenticated', - description: 'The user does not have an active session or is not authenticated', - }) - - // Run queries with RLS on the server - const { data } = await supabase.from('test').select('*') - res.json(data) -} - -export default ProtectedRoute -``` - - - - -## Auth with Next.js Middleware - -As an alternative to protecting individual pages you can use a [Next.js Middleware](https://nextjs.org/docs/middleware) to protect the entire directory or those that match the config object. In the following example, all requests to `/middleware-protected/*` will check whether a user is signed in, if successful the request will be forwarded to the destination route, otherwise the user will be redirected: - -```ts title=middleware.ts -import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs' +```jsx title="app/api/todos/route.jsx" +import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs' import { NextResponse } from 'next/server' -import type { NextRequest } from 'next/server' +import { cookies } from 'next/headers' -export async function middleware(req: NextRequest) { - // We need to create a response and hand it to the supabase client to be able to modify the response headers. - const res = NextResponse.next() - // Create authenticated Supabase Client. - const supabase = createMiddlewareSupabaseClient({ req, res }) - // Check if we have a session - const { - data: { session }, - } = await supabase.auth.getSession() - - // Check auth condition - if (session?.user.email?.endsWith('@gmail.com')) { - // Authentication successful, forward request to protected route. - return res - } - - // Auth condition not met, redirect to home page. - const redirectUrl = req.nextUrl.clone() - redirectUrl.pathname = '/' - redirectUrl.searchParams.set(`redirectedFrom`, req.nextUrl.pathname) - return NextResponse.redirect(redirectUrl) -} - -export const config = { - matcher: '/middleware-protected/:path*', -} -``` - -## Migration Guide - -### Migrating to v0.5.X - -To make these helpers more flexible as well as more maintainable and easier to upgrade for new versions of Next.js, we're stripping them down to the most useful part which is managing the cookies and giving you an authenticated supabase-js client in any environment (client, server, middleware/edge). - -Therefore we're marking the `withApiAuth`, `withPageAuth`, and `withMiddlewareAuth` higher order functions as deprecated and they will be removed in the next **minor** release (v0.6.X). - -Please follow the steps below to update your API routes, pages, and middleware handlers. Thanks! - -#### `withApiAuth` deprecated! - -Use `createServerSupabaseClient` within your `NextApiHandler`: - - - - -```tsx title=pages/api/protected-route.ts -import { withApiAuth } from '@supabase/auth-helpers-nextjs' - -export default withApiAuth(async function ProtectedRoute(req, res, supabase) { - // Run queries with RLS on the server - const { data } = await supabase.from('test').select('*') - res.json(data) -}) -``` - - - - -```tsx title=pages/api/protected-route.ts -import { NextApiHandler } from 'next' -import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs' - -const ProtectedRoute: NextApiHandler = async (req, res) => { - // Create authenticated Supabase Client - const supabase = createServerSupabaseClient({ req, res }) - // Check if we have a session - const { - data: { session }, - } = await supabase.auth.getSession() - - if (!session) - return res.status(401).json({ - error: 'not_authenticated', - description: 'The user does not have an active session or is not authenticated', - }) - - // Run queries with RLS on the server - const { data } = await supabase.from('test').select('*') - res.json(data) -} - -export default ProtectedRoute -``` - - - - -#### `withPageAuth` deprecated! - -Use `createServerSupabaseClient` within `getServerSideProps`: - - - - -```tsx title=pages/profile.tsx -import { withPageAuth, User } from '@supabase/auth-helpers-nextjs' - -export default function Profile({ user }: { user: User }) { - return
{JSON.stringify(user, null, 2)}
-} - -export const getServerSideProps = withPageAuth({ redirectTo: '/' }) -``` - -
- - -```tsx title=pages/profile.js -import { createServerSupabaseClient, User } from '@supabase/auth-helpers-nextjs' -import { GetServerSidePropsContext } from 'next' - -export default function Profile({ user }: { user: User }) { - return
{JSON.stringify(user, null, 2)}
-} - -export const getServerSideProps = async (ctx: GetServerSidePropsContext) => { - // Create authenticated Supabase Client - const supabase = createServerSupabaseClient(ctx) - // Check if we have a session - const { - data: { session }, - } = await supabase.auth.getSession() - - if (!session) - return { - redirect: { - destination: '/', - permanent: false, - }, - } - - return { - props: { - initialSession: session, - user: session.user, - }, - } +export async function POST(request) { + const { title } = await request.json() + const supabase = createRouteHandlerClient({ cookies }) + const { data } = await supabase.from('todos').insert({ title }).select() + return NextResponse.json(data) } ```
-
-#### `withMiddlewareAuth` deprecated! + - - - -```tsx title=middleware.ts -import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs' - -export const middleware = withMiddlewareAuth({ - redirectTo: '/', - authGuard: { - isPermitted: async (user) => { - return user.email?.endsWith('@gmail.com') ?? false - }, - redirectTo: '/insufficient-permissions', - }, -}) - -export const config = { - matcher: '/middleware-protected', -} -``` - - - - -```tsx title=middleware.ts -import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs' +```tsx title="app/api/todos/route.tsx" +import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs' import { NextResponse } from 'next/server' -import type { NextRequest } from 'next/server' +import { cookies } from 'next/headers' -export async function middleware(req: NextRequest) { - // We need to create a response and hand it to the supabase client to be able to modify the response headers. - const res = NextResponse.next() - // Create authenticated Supabase Client. - const supabase = createMiddlewareSupabaseClient({ req, res }) - // Check if we have a session - const { - data: { session }, - } = await supabase.auth.getSession() +import type { Database } from '@/lib/database.types' - // Check auth condition - if (session?.user.email?.endsWith('@gmail.com')) { - // Authentication successful, forward request to protected route. - return res - } - - // Auth condition not met, redirect to home page. - const redirectUrl = req.nextUrl.clone() - redirectUrl.pathname = '/' - redirectUrl.searchParams.set(`redirectedFrom`, req.nextUrl.pathname) - return NextResponse.redirect(redirectUrl) -} - -export const config = { - matcher: '/middleware-protected', +export async function POST(request: Request) { + const { title } = await request.json() + const supabase = createRouteHandlerClient({ cookies }) + const { data } = await supabase.from('todos').insert({ title }).select() + return NextResponse.json(data) } ``` +> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createRouteHandlerClient` to add type support to the Supabase client. + -### Migrating to v0.4.X and supabase-js v2 +### Middleware -With the update to `supabase-js` v2 the `auth` API routes are no longer required, therefore you can go ahead and delete your `auth` directory under the `/pages/api/` directory. Please refer to the [v2 migration guide](/docs/reference/javascript/v1/upgrade-guide) for the full set of changes within supabase-js. +See [refreshing session example](/docs/guides/auth/auth-helpers/nextjs#refresh-session-with-middleware) above. -The `/api/auth/logout` API route has been removed, please use the `signout` method instead: +## More examples -```jsx - -``` - -The `supabaseClient` and `supabaseServerClient` have been removed in favor of the `createBrowserSupabaseClient` and `createServerSupabaseClient` methods. This allows you to provide the CLI-generated types to the client: - -```tsx -// client-side -import type { Database } from 'types_db' -const [supabaseClient] = useState(() => createBrowserSupabaseClient()) - -// server-side API route -import type { NextApiRequest, NextApiResponse } from 'next' -import type { Database } from 'types_db' - -export default async (req: NextApiRequest, res: NextApiResponse) => { - const supabaseServerClient = createServerSupabaseClient({ - req, - res, - }) - const { - data: { user }, - } = await supabaseServerClient.auth.getUser() - - res.status(200).json({ name: user?.name ?? '' }) -} -``` - -- The `UserProvider` has been replaced by the `SessionContextProvider`. Make sure to wrap your `pages/_app.js` componenent with the `SessionContextProvider`. Then, throughout your application you can use the `useSessionContext` hook to get the `session` and the `useSupabaseClient` hook to get an authenticated `supabaseClient`. -- The `useUser` hook now returns the `user` object or `null`. -- Usage with TypeScript: You can pass types that were [generated with the Supabase CLI](/docs/reference/javascript/typescript-support#generating-types) to the Supabase Client to get enhanced type safety and auto completion: - -Creating a new supabase client object: - -```tsx -import { Database } from '../database.types' - -const [supabaseClient] = useState(() => createBrowserSupabaseClient()) -``` - -Retrieving a supabase client object from the SessionContext: - -```tsx -import { useSupabaseClient } from '@supabase/auth-helpers-react' -import { Database } from '../database.types' - -const supabaseClient = useSupabaseClient() -``` +- [Full App Router repo](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs) +- [Realtime](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/realtime-posts.tsx) +- [Protected Routes](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/page.tsx) export const Page = ({ children }) => diff --git a/apps/docs/public/sitemap.xml b/apps/docs/public/sitemap.xml index c3d36c42b96..e3d783ddd46 100644 --- a/apps/docs/public/sitemap.xml +++ b/apps/docs/public/sitemap.xml @@ -835,13 +835,13 @@ - https://supabase.com/docs/guides/auth/auth-helpers/nextjs-server-components + https://supabase.com/docs/guides/auth/auth-helpers/nextjs weekly 0.5 - https://supabase.com/docs/guides/auth/auth-helpers/nextjs + https://supabase.com/docs/guides/auth/auth-helpers/nextjs-pages weekly 0.5