Supabase Auth with Next.js Pages Directory
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 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, 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:
_38import { createPagesServerClient } from '@supabase/auth-helpers-nextjs'_38_38export default function ProtectedPage({ user, data }) {_38 return (_38 <>_38 <div>Protected content for {user.email}</div>_38 <pre>{JSON.stringify(data, null, 2)}</pre>_38 <pre>{JSON.stringify(user, null, 2)}</pre>_38 </>_38 )_38}_38_38export const getServerSideProps = async (ctx) => {_38 // Create authenticated Supabase Client_38 const supabase = createPagesServerClient(ctx)_38 // Check if we have a session_38 const {_38 data: { user },_38 } = await supabase.auth.getUser()_38_38 if (!session)_38 return {_38 redirect: {_38 destination: '/',_38 permanent: false,_38 },_38 }_38_38 // Run queries with RLS on the server_38 const { data } = await supabase.from('users').select('*')_38_38 return {_38 props: {_38 user,_38 data: data ?? [],_38 },_38 }_38}
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.
_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
component 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>()