Supabase Auth with Next.js
This submodule provides convenience helpers for implementing user authentication in Next.js applications.
Install the Next.js helper library#
1npm install @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 supported except for the new
app
directory approach. We're working on adding support for this and you can follow along here.
Additionally, install the React Auth Helpers for components and hooks that can be used across all React-based frameworks.
1npm 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.localNEXT_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:
pages/_app.js1import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
2import { SessionContextProvider } from '@supabase/auth-helpers-react'
3
4function MyApp({ Component, pageProps }) {
5 const router = useRouter()
6 // Create a new supabase browser client on every first render.
7 const [supabaseClient] = useState(() => createBrowserSupabaseClient())
8
9 return (
10 <SessionContextProvider
11 supabaseClient={supabaseClient}
12 initialSession={pageProps.initialSession}
13 >
14 <Component {...pageProps} />
15 </SessionContextProvider>
16 )
17}
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 to the Supabase Client to get enhanced type safety and auto completion:
Browser client#
Creating a new supabase client object:
1import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs' 2import { Database } from '../database.types' 3 4const supabaseClient = createBrowserSupabaseClient<Database>()
Retrieving a supabase client object from the SessionContext:
1import { useSupabaseClient } from '@supabase/auth-helpers-react' 2import { Database } from '../database.types' 3 4const supabaseClient = useSupabaseClient<Database>()
Server client#
1// Creating a new supabase server client object (e.g. in API route):
2import type { NextApiRequest, NextApiResponse } from 'next'
3import type { Database } from 'types_db'
4
5export default async (req: NextApiRequest, res: NextApiResponse) => {
6 const supabaseServerClient = createServerSupabaseClient<Database>({
7 req,
8 res,
9 })
10 const {
11 data: { user },
12 } = await supabaseServerClient.auth.getUser()
13
14 res.status(200).json({ name: user?.name ?? '' })
15}
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:
1import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
2import { useUser, useSupabaseClient } from '@supabase/auth-helpers-react'
3import { useEffect, useState } from 'react'
4
5const LoginPage = () => {
6 const supabaseClient = useSupabaseClient()
7 const user = useUser()
8 const [data, setData] = useState()
9
10 useEffect(() => {
11 async function loadData() {
12 const { data } = await supabaseClient.from('test').select('*')
13 setData(data)
14 }
15 // Only run query once user is logged in.
16 if (user) loadData()
17 }, [user])
18
19 if (!user)
20 return (
21 <Auth
22 redirectTo="http://localhost:3000/"
23 appearance={{ theme: ThemeSupa }}
24 supabaseClient={supabaseClient}
25 providers={['google', 'github']}
26 socialLayout="horizontal"
27 />
28 )
29
30 return (
31 <>
32 <button onClick={() => supabaseClient.auth.signOut()}>Sign out</button>
33 <p>user:</p>
34 <pre>{JSON.stringify(user, null, 2)}</pre>
35 <p>client-side data fetching with RLS</p>
36 <pre>{JSON.stringify(data, null, 2)}</pre>
37 </>
38 )
39}
40
41export default LoginPage
Server-side rendering (SSR)#
Create a server supabase client to retrieve the logged in user's session:
pages/profile.js1import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs'
2
3export default function Profile({ user }) {
4 return <div>Hello {user.name}</div>
5}
6
7export const getServerSideProps = async (ctx) => {
8 // Create authenticated Supabase Client
9 const supabase = createServerSupabaseClient(ctx)
10 // Check if we have a session
11 const {
12 data: { session },
13 } = await supabase.auth.getSession()
14
15 if (!session)
16 return {
17 redirect: {
18 destination: '/',
19 permanent: false,
20 },
21 }
22
23 return {
24 props: {
25 initialSession: session,
26 user: session.user,
27 },
28 }
29}
Server-side data fetching with RLS#
You can use the server supabase client to run row level security authenticated queries server-side:
1import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs'
2
3export default function ProtectedPage({ user, data }) {
4 return (
5 <>
6 <div>Protected content for {user.email}</div>
7 <pre>{JSON.stringify(data, null, 2)}</pre>
8 <pre>{JSON.stringify(user, null, 2)}</pre>
9 </>
10 )
11}
12
13export const getServerSideProps = async (ctx) => {
14 // Create authenticated Supabase Client
15 const supabase = createServerSupabaseClient(ctx)
16 // Check if we have a session
17 const {
18 data: { session },
19 } = await supabase.auth.getSession()
20
21 if (!session)
22 return {
23 redirect: {
24 destination: '/',
25 permanent: false,
26 },
27 }
28
29 // Run queries with RLS on the server
30 const { data } = await supabase.from('users').select('*')
31
32 return {
33 props: {
34 initialSession: session,
35 user: session.user,
36 data: data ?? [],
37 },
38 }
39}
Server-side data fetching to OAuth APIs using 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.
1import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs'
2
3export default function ProtectedPage({ user, allRepos }) {
4 return (
5 <>
6 <div>Protected content for {user.email}</div>
7 <p>Data fetched with provider token:</p>
8 <pre>{JSON.stringify(allRepos, null, 2)}</pre>
9 <p>user:</p>
10 <pre>{JSON.stringify(user, null, 2)}</pre>
11 </>
12 )
13}
14
15export const getServerSideProps = async (ctx) => {
16 // Create authenticated Supabase Client
17 const supabase = createServerSupabaseClient(ctx)
18 // Check if we have a session
19 const {
20 data: { session },
21 } = await supabase.auth.getSession()
22
23 if (!session)
24 return {
25 redirect: {
26 destination: '/',
27 permanent: false,
28 },
29 }
30
31 // Retrieve provider_token & logged in user's third-party id from metadata
32 const { provider_token, user } = session
33 const userId = user.user_metadata.user_name
34
35 const allRepos = await (
36 await fetch(`https://api.github.com/search/repositories?q=user:${userId}`, {
37 method: 'GET',
38 headers: {
39 Authorization: `token ${provider_token}`,
40 },
41 })
42 ).json()
43
44 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.js1import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs'
2
3const ProtectedRoute = async (req, res) => {
4 // Create authenticated Supabase Client
5 const supabase = createServerSupabaseClient({ req, res })
6 // Check if we have a session
7 const {
8 data: { session },
9 } = await supabase.auth.getSession()
10
11 if (!session)
12 return res.status(401).json({
13 error: 'not_authenticated',
14 description: 'The user does not have an active session or is not authenticated',
15 })
16
17 // Run queries with RLS on the server
18 const { data } = await supabase.from('test').select('*')
19 res.json(data)
20}
21
22export 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.ts1import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs'
2import { NextResponse } from 'next/server'
3import type { NextRequest } from 'next/server'
4
5export async function middleware(req: NextRequest) {
6 // We need to create a response and hand it to the supabase client to be able to modify the response headers.
7 const res = NextResponse.next()
8 // Create authenticated Supabase Client.
9 const supabase = createMiddlewareSupabaseClient({ req, res })
10 // Check if we have a session
11 const {
12 data: { session },
13 } = await supabase.auth.getSession()
14
15 // Check auth condition
16 if (session?.user.email?.endsWith('@gmail.com')) {
17 // Authentication successful, forward request to protected route.
18 return res
19 }
20
21 // Auth condition not met, redirect to home page.
22 const redirectUrl = req.nextUrl.clone()
23 redirectUrl.pathname = '/'
24 redirectUrl.searchParams.set(`redirectedFrom`, req.nextUrl.pathname)
25 return NextResponse.redirect(redirectUrl)
26}
27
28export const config = {
29 matcher: '/middleware-protected',
30}
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 deprectaed 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
:
pages/api/protected-route.ts1import { withApiAuth } from '@supabase/auth-helpers-nextjs'
2
3export default withApiAuth(async function ProtectedRoute(req, res, supabase) {
4 // Run queries with RLS on the server
5 const { data } = await supabase.from('test').select('*')
6 res.json(data)
7})
withPageAuth
deprecated!
Use createServerSupabaseClient
within getServerSideProps
:
pages/profile.tsx1import { withPageAuth, User } from '@supabase/auth-helpers-nextjs'
2
3export default function Profile({ user }: { user: User }) {
4 return <pre>{JSON.stringify(user, null, 2)}</pre>
5}
6
7export const getServerSideProps = withPageAuth({ redirectTo: '/' })
withMiddlewareAuth
deprecated!
middleware.ts1import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs' 2 3export const middleware = withMiddlewareAuth({ 4 redirectTo: '/', 5 authGuard: { 6 isPermitted: async (user) => { 7 return user.email?.endsWith('@gmail.com') ?? false 8 }, 9 redirectTo: '/insufficient-permissions', 10 }, 11}) 12 13export const config = { 14 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:
1<button 2 onClick={async () => { 3 await supabaseClient.auth.signOut() 4 router.push('/') 5 }} 6> 7 Logout 8</button>
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:
1// client-side
2import type { Database } from 'types_db'
3const [supabaseClient] = useState(() => createBrowserSupabaseClient<Database>())
4
5// server-side API route
6import type { NextApiRequest, NextApiResponse } from 'next'
7import type { Database } from 'types_db'
8
9export default async (req: NextApiRequest, res: NextApiResponse) => {
10 const supabaseServerClient = createServerSupabaseClient<Database>({
11 req,
12 res,
13 })
14 const {
15 data: { user },
16 } = await supabaseServerClient.auth.getUser()
17
18 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:
1import { Database } from '../database.types'
2
3const [supabaseClient] = useState(() => createBrowserSupabaseClient<Database>())
Retrieving a supabase client object from the SessionContext:
1import { useSupabaseClient } from '@supabase/auth-helpers-react' 2import { Database } from '../database.types' 3 4const supabaseClient = useSupabaseClient<Database>()