--- 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)' sitemapPriority: 0.5 --- The `auth-helpers` package has been replaced with the `@supabase/ssr` package. We recommend setting up Auth for your Next.js app with `@supabase/ssr` instead. See the [Next.js Server-Side Auth guide](/docs/guides/auth/server-side/nextjs?router=pages) to learn how. 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. This is now the recommended path for new Next.js app. 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 Terminal npm install @supabase/auth-helpers-nextjs @supabase/supabase-js ``` 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 Terminal npm install @supabase/auth-helpers-react ``` ## Set up environment variables Retrieve your project URL and anon key in your project's [API settings](/dashboard/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 .env.local NEXT_PUBLIC_SUPABASE_URL=your-supabase-url NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your-supabase-publishable-key ``` ## Basic setup Wrap your `pages/_app.js` component with the `SessionContextProvider` component: ```jsx pages/_app.js import { createPagesBrowserClient } 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(() => createPagesBrowserClient()) return ( ) } ``` Wrap your `pages/_app.tsx` component with the `SessionContextProvider` component: ```tsx import { type AppProps } from 'next/app' import { createPagesBrowserClient } 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(() => createPagesBrowserClient()) return ( ) } export default MyApp ``` You can now determine if a user is authenticated by checking that the `user` object returned by the `useUser()` hook is defined. ### Code Exchange API route The `Code Exchange` API route is required for the [server-side auth flow](/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. Create a new file at `pages/api/auth/callback.js` and populate with the following: ```jsx pages/api/auth/callback.js import { NextApiHandler } from 'next' import { createPagesServerClient } from '@supabase/auth-helpers-nextjs' const handler = async (req, res) => { const { code } = req.query if (code) { const supabase = createPagesServerClient({ req, res }) await supabase.auth.exchangeCodeForSession(String(code)) } res.redirect('/') } export default handler ``` Create a new file at `pages/api/auth/callback.ts` and populate with the following: ```tsx pages/api/auth/callback.ts import { NextApiHandler } from 'next' import { createPagesServerClient } from '@supabase/auth-helpers-nextjs' const handler: NextApiHandler = async (req, res) => { const { code } = req.query if (code) { const supabase = createPagesServerClient({ req, res }) await supabase.auth.exchangeCodeForSession(String(code)) } res.redirect('/') } export default handler ``` ## 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 { createPagesBrowserClient } from '@supabase/auth-helpers-nextjs' import { Database } from '../database.types' const supabaseClient = createPagesBrowserClient() ``` 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 { createPagesServerClient } 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 = createPagesServerClient({ 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 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 pages/profile.js import { createPagesServerClient } 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 = createPagesServerClient(ctx) // Check if we have a user const { data: { user }, } = await supabase.auth.getUser() if (!user) return { redirect: { destination: '/', permanent: false, }, } return { props: { 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 { createPagesServerClient } 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 = createPagesServerClient(ctx) // Check if we have a session const { data: { user }, } = await supabase.auth.getUser() if (!session) return { redirect: { destination: '/', permanent: false, }, } // Run queries with RLS on the server const { data } = await supabase.from('users').select('*') return { props: { user, data: data ?? [], }, } } ```
```tsx import { User, createPagesServerClient } 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 = createPagesServerClient(ctx) // Check if we have a session const { data: { user }, } = await supabase.auth.getUser() if (!user) return { redirect: { destination: '/', permanent: false, }, } // Run queries with RLS on the server const { data } = await supabase.from('users').select('*') return { props: { 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. Note that the server accesses data on the session object returned by `auth.getSession`. This data should normally not be trusted, because it is read from the local storage medium. It is not revalidated against the Auth server unless the session is expired, which means the sender can tamper with it. In this case, the third-party API will validate the `provider_token`, and a malicious actor is unable to forge one. ```jsx import { createPagesServerClient } 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 = createPagesServerClient(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, createPagesServerClient } 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 = createPagesServerClient(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 pages/api/protected-route.js import { createPagesServerClient } from '@supabase/auth-helpers-nextjs' const ProtectedRoute = async (req, res) => { // Create authenticated Supabase Client const supabase = createPagesServerClient({ req, res }) // Check if we have a user const { data: { user }, } = await supabase.auth.getUser() if (!user) 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 pages/api/protected-route.ts import { NextApiHandler } from 'next' import { createPagesServerClient } from '@supabase/auth-helpers-nextjs' const ProtectedRoute: NextApiHandler = async (req, res) => { // Create authenticated Supabase Client const supabase = createPagesServerClient({ req, res }) // Check if we have a session const { data: { user }, } = await supabase.auth.getUser() if (!user) 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 middleware.ts import { createMiddlewareClient } 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 = createMiddlewareClient({ req, res }) // Check if we have a session const { data: { user }, } = await supabase.auth.getUser() // Check auth condition if (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.7.X #### PKCE Auth flow PKCE is the new server-side auth flow implemented by the Next.js Auth Helpers. It requires a new API route for `/api/auth/callback` that exchanges an auth `code` for the user's `session`. Check the [Code Exchange API Route steps](/docs/guides/auth/auth-helpers/nextjs-pages#code-exchange-api-route) above to implement this route. #### Authentication For authentication methods that have a `redirectTo` or `emailRedirectTo`, this must be set to this new code exchange API Route - `/api/auth/callback`. This is an example with the `signUp` function: ```jsx supabase.auth.signUp({ email: 'valid.email@supabase.io', password: 'sup3rs3cur3', options: { emailRedirectTo: 'http://localhost:3000/auth/callback', }, }) ``` #### Deprecated functions With v0.7.x of the Next.js Auth Helpers a new naming convention has been implemented for `createClient` functions. The `createBrowserSupabaseClient` and `createServerSupabaseClient` functions have been marked as deprecated, and will be removed in a future version of the Auth Helpers. - `createBrowserSupabaseClient` has been replaced with `createPagesBrowserClient` - `createServerSupabaseClient` has been replaced with `createPagesServerClient` ### 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). Follow the steps below to update your API routes, pages, and middleware handlers. Thanks! #### `withApiAuth` deprecated! Use `createPagesServerClient` within your `NextApiHandler`: ```tsx 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 pages/api/protected-route.ts import { NextApiHandler } from 'next' import { createPagesServerClient } from '@supabase/auth-helpers-nextjs' const ProtectedRoute: NextApiHandler = async (req, res) => { // Create authenticated Supabase Client const supabase = createPagesServerClient({ req, res }) // Check if we have a session const { data: { user }, } = await supabase.auth.getUser() if (!user) 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 `createPagesServerClient` within `getServerSideProps`: ```tsx 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 pages/profile.js import { createPagesServerClient, 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 = createPagesServerClient(ctx) // Check if we have a session const { data: { user }, } = await supabase.auth.getUser() if (!user) return { redirect: { destination: '/', permanent: false, }, } return { props: { initialSession: session, user: session.user, }, } } ```
#### `withMiddlewareAuth` deprecated! ```tsx 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 middleware.ts import { createMiddlewareClient } 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 = createMiddlewareClient({ req, res }) // Check if we have a session const { data: { user }, } = await supabase.auth.getUser() // Check auth condition if (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. 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, use the `signout` method instead: ```jsx ``` The `supabaseClient` and `supabaseServerClient` have been removed in favor of the `createPagesBrowserClient` and `createPagesServerClient` 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(() => createPagesBrowserClient()) // 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 = createPagesServerClient({ 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` component 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(() => createPagesBrowserClient()) ``` Retrieving a `supabase` client object from the `SessionContext`: ```tsx import { useSupabaseClient } from '@supabase/auth-helpers-react' import { Database } from '../database.types' const supabaseClient = useSupabaseClient() ```