Supabase Auth with Next.js Pages Directory
caution
We generally recommend using the new @supabase/ssr
package instead of auth-helpers
. @supabase/ssr
takes the core concepts of the Auth Helpers package and makes them available to any server language or framework. Check out the migration doc to learn more.
This submodule provides convenience helpers for implementing user authentication in Next.js applications using the pages directory.
Note: As of Next.js 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.
Install the Next.js helper library#
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.
Set up environment variables#
Retrieve your project URL and anon key in your project's API settings 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.
Basic Setup#
Wrap your pages/_app.js
component with the SessionContextProvider
component:
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 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:
Usage with TypeScript#
You can pass types that were generated with the Supabase CLI to the Supabase Client to get enhanced type safety and auto completion:
Browser client#
Creating a new supabase client object:
_10import { createPagesBrowserClient } from '@supabase/auth-helpers-nextjs'_10import { Database } from '../database.types'_10_10const supabaseClient = createPagesBrowserClient<Database>()
Retrieving a supabase client object from the SessionContext:
_10import { useSupabaseClient } from '@supabase/auth-helpers-react'_10import { Database } from '../database.types'_10_10const supabaseClient = useSupabaseClient<Database>()
Server client#
_16// Creating a new supabase server client object (e.g. in API route):_16import { createPagesServerClient } from '@supabase/auth-helpers-nextjs'_16import type { NextApiRequest, NextApiResponse } from 'next'_16import type { Database } from 'types_db'_16_16export default async (req: NextApiRequest, res: NextApiResponse) => {_16 const supabaseServerClient = createPagesServerClient<Database>({_16 req,_16 res,_16 })_16 const {_16 data: { user },_16 } = await supabaseServerClient.auth.getUser()_16_16 res.status(200).json({ name: user?.name ?? '' })_16}
Client-side data fetching with RLS#
For 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:
_42import { Auth } from '@supabase/auth-ui-react'_42import { ThemeSupa } from '@supabase/auth-ui-shared'_42import { useUser, useSupabaseClient } from '@supabase/auth-helpers-react'_42import { useEffect, useState } from 'react'_42_42const LoginPage = () => {_42 const supabaseClient = useSupabaseClient()_42 const user = useUser()_42 const [data, setData] = useState()_42_42 useEffect(() => {_42 async function loadData() {_42 const { data } = await supabaseClient.from('test').select('*')_42 setData(data)_42 }_42 // Only run query once user is logged in._42 if (user) loadData()_42 }, [user])_42_42 if (!user)_42 return (_42 <Auth_42 redirectTo="http://localhost:3000/"_42 appearance={{ theme: ThemeSupa }}_42 supabaseClient={supabaseClient}_42 providers={['google', 'github']}_42 socialLayout="horizontal"_42 />_42 )_42_42 return (_42 <>_42 <button onClick={() => supabaseClient.auth.signOut()}>Sign out</button>_42 <p>user:</p>_42 <pre>{JSON.stringify(user, null, 2)}</pre>_42 <p>client-side data fetching with RLS</p>_42 <pre>{JSON.stringify(data, null, 2)}</pre>_42 </>_42 )_42}_42_42export default LoginPage
Server-side rendering (SSR)#
Create a server supabase client to retrieve the logged in user's session:
Server-side data fetching with RLS#
You can use the server supabase client to run row level security authenticated queries server-side:
_39import { createPagesServerClient } from '@supabase/auth-helpers-nextjs'_39_39export default function ProtectedPage({ user, data }) {_39 return (_39 <>_39 <div>Protected content for {user.email}</div>_39 <pre>{JSON.stringify(data, null, 2)}</pre>_39 <pre>{JSON.stringify(user, null, 2)}</pre>_39 </>_39 )_39}_39_39export const getServerSideProps = async (ctx) => {_39 // Create authenticated Supabase Client_39 const supabase = createPagesServerClient(ctx)_39 // Check if we have a session_39 const {_39 data: { session },_39 } = await supabase.auth.getSession()_39_39 if (!session)_39 return {_39 redirect: {_39 destination: '/',_39 permanent: false,_39 },_39 }_39_39 // Run queries with RLS on the server_39 const { data } = await supabase.from('users').select('*')_39_39 return {_39 props: {_39 initialSession: session,_39 user: session.user,_39 data: data ?? [],_39 },_39 }_39}
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.
_45import { createPagesServerClient } from '@supabase/auth-helpers-nextjs'_45_45export default function ProtectedPage({ user, allRepos }) {_45 return (_45 <>_45 <div>Protected content for {user.email}</div>_45 <p>Data fetched with provider token:</p>_45 <pre>{JSON.stringify(allRepos, null, 2)}</pre>_45 <p>user:</p>_45 <pre>{JSON.stringify(user, null, 2)}</pre>_45 </>_45 )_45}_45_45export const getServerSideProps = async (ctx) => {_45 // Create authenticated Supabase Client_45 const supabase = createPagesServerClient(ctx)_45 // Check if we have a session_45 const {_45 data: { session },_45 } = await supabase.auth.getSession()_45_45 if (!session)_45 return {_45 redirect: {_45 destination: '/',_45 permanent: false,_45 },_45 }_45_45 // Retrieve provider_token & logged in user's third-party id from metadata_45 const { provider_token, user } = session_45 const userId = user.user_metadata.user_name_45_45 const allRepos = await (_45 await fetch(`https://api.github.com/search/repositories?q=user:${userId}`, {_45 method: 'GET',_45 headers: {_45 Authorization: `token ${provider_token}`,_45 },_45 })_45 ).json()_45_45 return { props: { user, allRepos } }_45}
Protecting API routes#
Create a server supabase client to retrieve the logged in user's session:
Auth with Next.js Middleware#
As an alternative to protecting individual pages you can use a Next.js 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:
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 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:
_10supabase.auth.signUp({_10 email: 'jon@example.com',_10 password: 'sup3rs3cur3',_10 options: {_10 emailRedirectTo: 'http://localhost:3000/auth/callback',_10 },_10})
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 withcreatePagesBrowserClient
createServerSupabaseClient
has been replaced withcreatePagesServerClient
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 createPagesServerClient
within your NextApiHandler
:
withPageAuth
deprecated!#
Use createPagesServerClient
within getServerSideProps
:
withMiddlewareAuth
deprecated!#
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 for the full set of changes within supabase-js.
The /api/auth/logout
API route has been removed, please use the signout
method instead:
_10<button_10 onClick={async () => {_10 await supabaseClient.auth.signOut()_10 router.push('/')_10 }}_10>_10 Logout_10</button>
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:
_19// client-side_19import type { Database } from 'types_db'_19const [supabaseClient] = useState(() => createPagesBrowserClient<Database>())_19_19// server-side API route_19import type { NextApiRequest, NextApiResponse } from 'next'_19import type { Database } from 'types_db'_19_19export default async (req: NextApiRequest, res: NextApiResponse) => {_19 const supabaseServerClient = createPagesServerClient<Database>({_19 req,_19 res,_19 })_19 const {_19 data: { user },_19 } = await supabaseServerClient.auth.getUser()_19_19 res.status(200).json({ name: user?.name ?? '' })_19}
- The
UserProvider
has been replaced by theSessionContextProvider
. Make sure to wrap yourpages/_app.js
componenent with theSessionContextProvider
. Then, throughout your application you can use theuseSessionContext
hook to get thesession
and theuseSupabaseClient
hook to get an authenticatedsupabaseClient
. - The
useUser
hook now returns theuser
object ornull
. - Usage with TypeScript: You can pass types that were generated with the Supabase CLI to the Supabase Client to get enhanced type safety and auto completion:
Creating a new supabase client object:
_10import { Database } from '../database.types'_10_10const [supabaseClient] = useState(() => createPagesBrowserClient<Database>())
Retrieving a supabase client object from the SessionContext:
_10import { useSupabaseClient } from '@supabase/auth-helpers-react'_10import { Database } from '../database.types'_10_10const supabaseClient = useSupabaseClient<Database>()