Auth

Launch Week

Supabase Launch Week X Logo
Dec 11-15 / 8am PT

Supabase Auth with the Next.js App Router

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 framework. Check out the migration doc to learn more.

The Next.js Auth Helpers package configures Supabase Auth to store the user's session in a cookie, rather than localStorage. This makes it available across the client and server of the App Router - Client Components, Server Components, Server Actions, Route Handlers and Middleware. The session is automatically sent along with any requests to Supabase.

note

If you are using the pages directory, check out Auth Helpers in Next.js Pages Directory.

Install Next.js Auth Helpers library#

Terminal
npm install @supabase/auth-helpers-nextjs @supabase/supabase-js

Declare Environment Variables#

Retrieve your project's URL and anon key from your API settings, and create a .env.local file with the following environment variables:

.env.local
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key

Managing session with Middleware#

When using the Supabase client on the server, you must perform extra steps to ensure the user's auth session remains active. Since the user's session is tracked in a cookie, we need to read this cookie and update it if necessary.

Next.js Server Components allow you to read a cookie but not write back to it. Middleware on the other hand allow you to both read and write to cookies.

Next.js Middleware runs immediately before each route is rendered. We'll use Middleware to refresh the user's session before loading Server Component routes.

Create a new middleware.js file in the root of your project and populate with the following:

middleware.js
import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'

export async function middleware(req) {
const res = NextResponse.next()
const supabase = createMiddlewareClient({ req, res })
await supabase.auth.getSession()
return res
}

note

The getSession function must be called for any Server Component routes that use a Supabase client.

Managing sign-in with Code Exchange#

The Next.js Auth Helpers are configured to use the server-side auth flow to sign users into your application. This requires you to setup a Code Exchange route, to exchange an auth code for the user's session, which is set as a cookie for future requests made to Supabase.

To make this work with Next.js, we create a callback Route Handler that performs this exchange:

Create a new file at app/auth/callback/route.js and populate with the following:

app/auth/callback/route.js
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'

export async function GET(request) {
const requestUrl = new URL(request.url)
const code = requestUrl.searchParams.get('code')

if (code) {
const cookieStore = cookies()
const supabase = createRouteHandlerClient({ cookies: () => cookieStore })
await supabase.auth.exchangeCodeForSession(code)
}

// URL to redirect to after sign in process completes
return NextResponse.redirect(requestUrl.origin)
}

Authentication#

Authentication can be initiated client or server-side. All of the supabase-js authentication strategies are supported with the Auth Helpers client.

note

The authentication flow requires the Code Exchange Route to exchange a code for the user's session.

Client-side#

Client Components can be used to trigger the authentication process from event handlers.

app/login/page.jsx
'use client'

import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'
import { useRouter } from 'next/navigation'
import { useState } from 'react'

export default function Login() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const router = useRouter()
const supabase = createClientComponentClient()

const handleSignUp = async () => {
await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${location.origin}/auth/callback`,
},
})
router.refresh()
}

const handleSignIn = async () => {
await supabase.auth.signInWithPassword({
email,
password,
})
router.refresh()
}

const handleSignOut = async () => {
await supabase.auth.signOut()
router.refresh()
}

return (
<>
<input name="email" onChange={(e) => setEmail(e.target.value)} value={email} />
<input
type="password"
name="password"
onChange={(e) => setPassword(e.target.value)}
value={password}
/>
<button onClick={handleSignUp}>Sign up</button>
<button onClick={handleSignIn}>Sign in</button>
<button onClick={handleSignOut}>Sign out</button>
</>
)
}

Server-side#

The combination of Server Components and Route Handlers can be used to trigger the authentication process from form submissions.

Sign Up Route#

app/auth/sign-up/route.js
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'

export async function POST(request) {
const requestUrl = new URL(request.url)
const formData = await request.formData()
const email = formData.get('email')
const password = formData.get('password')
const cookieStore = cookies()
const supabase = createRouteHandlerClient({ cookies: () => cookieStore })

await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${requestUrl.origin}/auth/callback`,
},
})

return NextResponse.redirect(requestUrl.origin, {
status: 301,
})
}

note

Returning a 301 status redirects from a POST to a GET route

Login Route#

app/auth/login/route.js
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'

export async function POST(request) {
const requestUrl = new URL(request.url)
const formData = await request.formData()
const email = formData.get('email')
const password = formData.get('password')
const cookieStore = cookies()
const supabase = createRouteHandlerClient({ cookies: () => cookieStore })

await supabase.auth.signInWithPassword({
email,
password,
})

return NextResponse.redirect(requestUrl.origin, {
status: 301,
})
}

note

Returning a 301 status redirects from a POST to a GET route

Logout Route#

app/auth/logout/route.js
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
import { NextResponse } from 'next/server'

export async function POST(request) {
const requestUrl = new URL(request.url)
const cookieStore = cookies()
const supabase = createRouteHandlerClient({ cookies: () => cookieStore })

await supabase.auth.signOut()

return NextResponse.redirect(`${requestUrl.origin}/login`, {
status: 301,
})
}

Login Page#

app/login/page.jsx
export default function Login() {
return (
<form action="/auth/login" method="post">
<label htmlFor="email">Email</label>
<input name="email" />
<label htmlFor="password">Password</label>
<input type="password" name="password" />
<button>Sign In</button>
<button formAction="/auth/sign-up">Sign Up</button>
<button formAction="/auth/logout">Sign Out</button>
</form>
)
}

Creating a Supabase Client#

There are 5 ways to access the Supabase client with the Next.js Auth Helpers:

This allows for the Supabase client to be easily instantiated in the correct context. All you need to change is the context in the middle create[ClientComponent|ServerComponent|ServerAction|RouteHandler|Middleware]Client and the Auth Helpers will take care of the rest.

Client Components#

Client Components allow the use of client-side hooks - such as useEffect and useState. They can be used to request data from Supabase client-side, and subscribe to realtime events.

app/client/page.jsx
'use client'

import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'
import { useEffect, useState } from 'react'

export default function Page() {
const [todos, setTodos] = useState()
const supabase = createClientComponentClient()

useEffect(() => {
const getData = async () => {
const { data } = await supabase.from('todos').select()
setTodos(data)
}

getData()
}, [])

return todos ? <pre>{JSON.stringify(todos, null, 2)}</pre> : <p>Loading todos...</p>
}

note

Check out the Next.js auth example repo for more examples, including realtime subscriptions.

Singleton#

The createClientComponentClient function implements a Singleton pattern by default, meaning that all invocations will return the same Supabase client instance. If you need multiple Supabase instances across Client Components, you can pass an additional configuration option { isSingleton: false } to get a new client every time this function is called.

const supabase = createClientComponentClient({ isSingleton: false })

Server Components#

Server Components allow for asynchronous data to be fetched server-side.

note

In order to use Supabase in Server Components, you need to have implemented the Middleware steps above.

app/page.jsx
import { cookies } from 'next/headers'
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs'

export default async function Page() {
const cookieStore = cookies()
const supabase = createServerComponentClient({ cookies: () => cookieStore })
const { data } = await supabase.from('todos').select()
return <pre>{JSON.stringify(data, null, 2)}</pre>
}

note

Check out the Next.js auth example repo for more examples, including redirecting unauthenticated users - protected pages.

Server Actions#

Server Actions allow mutations to be performed server-side.

note

Next.js Server Actions are currently in alpha so may change without notice.

app/new-post/page.jsx
import { cookies } from 'next/headers'
import { createServerActionClient } from '@supabase/auth-helpers-nextjs'
import { revalidatePath } from 'next/cache'

export default async function NewTodo() {
const addTodo = async (formData) => {
'use server'

const title = formData.get('title')
const supabase = createServerActionClient({ cookies })
await supabase.from('todos').insert({ title })
revalidatePath('/')
}

return (
<form action={addTodo}>
<input name="title" />
</form>
)
}

Route Handlers#

Route Handlers replace API Routes and allow for logic to be performed server-side. They can respond to GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS requests.

app/api/todos/route.js
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'
import { cookies } from 'next/headers'

export async function POST(request) {
const { title } = await request.json()
const cookieStore = cookies()
const supabase = createRouteHandlerClient({ cookies: () => cookieStore })
const { data } = await supabase.from('todos').insert({ title }).select()
return NextResponse.json(data)
}

Middleware#

See refreshing session example above.

Edge Runtime#

The Next.js Edge Runtime allows you to host Server Components and Route Handlers from Edge nodes, serving the routes as close as possible to your user's location.

A route can be configured to use the Edge Runtime by exporting a runtime variable set to edge. Additionally, the cookies() function must be called from the Edge route, before creating a Supabase Client.

Server Components#

app/page.jsx
import { cookies } from 'next/headers'
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs'

export const runtime = 'edge'
export const dynamic = 'force-dynamic'

export default async function Page() {
const cookieStore = cookies()

const supabase = createServerComponentClient({
cookies: () => cookieStore,
})

const { data } = await supabase.from('todos').select()
return <pre>{JSON.stringify(data, null, 2)}</pre>
}

Route Handlers#

app/api/todos/route.js
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { NextResponse } from 'next/server'
import { cookies } from 'next/headers'

export const runtime = 'edge'
export const dynamic = 'force-dynamic'

export async function POST(request) {
const { title } = await request.json()
const cookieStore = cookies()
const supabase = createRouteHandlerClient({ cookies: () => cookieStore })

const { data } = await supabase.from('todos').insert({ title }).select()
return NextResponse.json(data)
}

Static Routes#

Server Components and Route Handlers are static by default - data is fetched once at build time and the value is cached. Since the request to Supabase now happens at build time, there is no user, session or cookie to pass along with the request to Supabase. Therefore, the createClient function from supabase-js can be used to fetch data for static routes.

Server Components#

app/page.jsx
import { createClient } from '@supabase/supabase-js'

export default async function Page() {
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
)

const { data } = await supabase.from('todos').select()
return <pre>{JSON.stringify(data, null, 2)}</pre>
}

Route Handlers#

app/api/todos/route.js
import { createClient } from '@supabase/supabase-js'
import { NextResponse } from 'next/server'

export async function POST(request) {
const { title } = await request.json()

const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
)

const { data } = await supabase.from('todos').insert({ title }).select()
return NextResponse.json(data)
}

More examples#

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 Route Handler for /auth/callback that exchanges an auth code for the user's session.

Check the Code Exchange Route steps above to implement this Route Handler.

Authentication#

For authentication methods that have a redirectTo or emailRedirectTo, this must be set to this new code exchange Route Handler - /auth/callback. This is an example with the signUp function:

supabase.auth.signUp({
email: 'jon@example.com',
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 createMiddlewareSupabaseClient, createBrowserSupabaseClient, createServerComponentSupabaseClient and createRouteHandlerSupabaseClient functions have been marked as deprecated, and will be removed in a future version of the Auth Helpers.

  • createMiddlewareSupabaseClient has been replaced with createMiddlewareClient
  • createBrowserSupabaseClient has been replaced with createClientComponentClient
  • createServerComponentSupabaseClient has been replaced with createServerComponentClient
  • createRouteHandlerSupabaseClient has been replaced with createRouteHandlerClient

createClientComponentClient returns singleton#

You no longer need to implement logic to ensure there is only a single instance of the Supabase Client shared across all Client Components - this is now the default and handled by the createClientComponentClient function. Call it as many times as you want!

"use client";

import { createClientComponentClient } from "@supabase/auth-helpers-nextjs";

export default function() {
const supabase = createClientComponentClient();
return ...
}

For an example of creating multiple Supabase clients, check Singleton section above.


We only collect analytics essential to ensuring smooth operation of our services.

Learn more