Home

Supabase Auth with Next.js Pages Directory

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#

Terminal

_10
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.

Terminal

_10
npm install @supabase/auth-helpers-react

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.

.env.local

_10
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
_10
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key

Basic setup#

Wrap your pages/_app.js component with the SessionContextProvider component:

pages/_app.js

_17
import { createPagesBrowserClient } from '@supabase/auth-helpers-nextjs'
_17
import { SessionContextProvider } from '@supabase/auth-helpers-react'
_17
import { useState } from 'react'
_17
_17
function MyApp({ Component, pageProps }) {
_17
// Create a new supabase browser client on every first render.
_17
const [supabaseClient] = useState(() => createPagesBrowserClient())
_17
_17
return (
_17
<SessionContextProvider
_17
supabaseClient={supabaseClient}
_17
initialSession={pageProps.initialSession}
_17
>
_17
<Component {...pageProps} />
_17
</SessionContextProvider>
_17
)
_17
}

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:

pages/api/auth/callback.js

_15
import { NextApiHandler } from 'next'
_15
import { createPagesServerClient } from '@supabase/auth-helpers-nextjs'
_15
_15
const handler = async (req, res) => {
_15
const { code } = req.query
_15
_15
if (code) {
_15
const supabase = createPagesServerClient({ req, res })
_15
await supabase.auth.exchangeCodeForSession(String(code))
_15
}
_15
_15
res.redirect('/')
_15
}
_15
_15
export default handler

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:


_10
import { createPagesBrowserClient } from '@supabase/auth-helpers-nextjs'
_10
import { Database } from '../database.types'
_10
_10
const supabaseClient = createPagesBrowserClient<Database>()

Retrieving a supabase client object from the SessionContext:


_10
import { useSupabaseClient } from '@supabase/auth-helpers-react'
_10
import { Database } from '../database.types'
_10
_10
const supabaseClient = useSupabaseClient<Database>()

Server client#


_16
// Creating a new supabase server client object (e.g. in API route):
_16
import { createPagesServerClient } from '@supabase/auth-helpers-nextjs'
_16
import type { NextApiRequest, NextApiResponse } from 'next'
_16
import type { Database } from 'types_db'
_16
_16
export 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:


_42
import { Auth } from '@supabase/auth-ui-react'
_42
import { ThemeSupa } from '@supabase/auth-ui-shared'
_42
import { useUser, useSupabaseClient } from '@supabase/auth-helpers-react'
_42
import { useEffect, useState } from 'react'
_42
_42
const 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
_42
export default LoginPage

Server-side rendering (SSR)#

Create a server supabase client to retrieve the logged in user's session:

pages/profile.js

_29
import { createPagesServerClient } from '@supabase/auth-helpers-nextjs'
_29
_29
export default function Profile({ user }) {
_29
return <div>Hello {user.name}</div>
_29
}
_29
_29
export const getServerSideProps = async (ctx) => {
_29
// Create authenticated Supabase Client
_29
const supabase = createPagesServerClient(ctx)
_29
// Check if we have a session
_29
const {
_29
data: { session },
_29
} = await supabase.auth.getSession()
_29
_29
if (!session)
_29
return {
_29
redirect: {
_29
destination: '/',
_29
permanent: false,
_29
},
_29
}
_29
_29
return {
_29
props: {
_29
initialSession: session,
_29
user: session.user,
_29
},
_29
}
_29
}

Server-side data fetching with RLS#

You can use the server supabase client to run row level security authenticated queries server-side:


_39
import { createPagesServerClient } from '@supabase/auth-helpers-nextjs'
_39
_39
export 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
_39
export 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.


_45
import { createPagesServerClient } from '@supabase/auth-helpers-nextjs'
_45
_45
export 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
_45
export 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:

pages/api/protected-route.js

_22
import { createPagesServerClient } from '@supabase/auth-helpers-nextjs'
_22
_22
const ProtectedRoute = async (req, res) => {
_22
// Create authenticated Supabase Client
_22
const supabase = createPagesServerClient({ req, res })
_22
// Check if we have a session
_22
const {
_22
data: { session },
_22
} = await supabase.auth.getSession()
_22
_22
if (!session)
_22
return res.status(401).json({
_22
error: 'not_authenticated',
_22
description: 'The user does not have an active session or is not authenticated',
_22
})
_22
_22
// Run queries with RLS on the server
_22
const { data } = await supabase.from('test').select('*')
_22
res.json(data)
_22
}
_22
_22
export default ProtectedRoute

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:

middleware.ts

_30
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
_30
import { NextResponse } from 'next/server'
_30
import type { NextRequest } from 'next/server'
_30
_30
export async function middleware(req: NextRequest) {
_30
// We need to create a response and hand it to the supabase client to be able to modify the response headers.
_30
const res = NextResponse.next()
_30
// Create authenticated Supabase Client.
_30
const supabase = createMiddlewareClient({ req, res })
_30
// Check if we have a session
_30
const {
_30
data: { session },
_30
} = await supabase.auth.getSession()
_30
_30
// Check auth condition
_30
if (session?.user.email?.endsWith('@gmail.com')) {
_30
// Authentication successful, forward request to protected route.
_30
return res
_30
}
_30
_30
// Auth condition not met, redirect to home page.
_30
const redirectUrl = req.nextUrl.clone()
_30
redirectUrl.pathname = '/'
_30
redirectUrl.searchParams.set(`redirectedFrom`, req.nextUrl.pathname)
_30
return NextResponse.redirect(redirectUrl)
_30
}
_30
_30
export const config = {
_30
matcher: '/middleware-protected/:path*',
_30
}

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:


_10
supabase.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 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).

Please follow the steps below to update your API routes, pages, and middleware handlers. Thanks!

withApiAuth deprecated!#

Use createPagesServerClient within your NextApiHandler:

pages/api/protected-route.ts

_10
import { withApiAuth } from '@supabase/auth-helpers-nextjs'
_10
_10
export default withApiAuth(async function ProtectedRoute(req, res, supabase) {
_10
// Run queries with RLS on the server
_10
const { data } = await supabase.from('test').select('*')
_10
res.json(data)
_10
})

withPageAuth deprecated!#

Use createPagesServerClient within getServerSideProps:

pages/profile.tsx

_10
import { withPageAuth, User } from '@supabase/auth-helpers-nextjs'
_10
_10
export default function Profile({ user }: { user: User }) {
_10
return <pre>{JSON.stringify(user, null, 2)}</pre>
_10
}
_10
_10
export const getServerSideProps = withPageAuth({ redirectTo: '/' })

withMiddlewareAuth deprecated!#

middleware.ts

_15
import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs'
_15
_15
export const middleware = withMiddlewareAuth({
_15
redirectTo: '/',
_15
authGuard: {
_15
isPermitted: async (user) => {
_15
return user.email?.endsWith('@gmail.com') ?? false
_15
},
_15
redirectTo: '/insufficient-permissions',
_15
},
_15
})
_15
_15
export const config = {
_15
matcher: '/middleware-protected',
_15
}

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
_19
import type { Database } from 'types_db'
_19
const [supabaseClient] = useState(() => createPagesBrowserClient<Database>())
_19
_19
// server-side API route
_19
import type { NextApiRequest, NextApiResponse } from 'next'
_19
import type { Database } from 'types_db'
_19
_19
export 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 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 to the Supabase Client to get enhanced type safety and auto completion:

Creating a new supabase client object:


_10
import { Database } from '../database.types'
_10
_10
const [supabaseClient] = useState(() => createPagesBrowserClient<Database>())

Retrieving a supabase client object from the SessionContext:


_10
import { useSupabaseClient } from '@supabase/auth-helpers-react'
_10
import { Database } from '../database.types'
_10
_10
const supabaseClient = useSupabaseClient<Database>()