Auth

Email Auth with PKCE flow for SSR

Learn how to configure email authentication in your server-side rendering (SSR) application to work with the PKCE flow.

Setting up SSR client

Check out our guide for creating a client to learn how to install the necessary packages, declare environment variables, and create a Supabase client configured for SSR in your framework.

Create API endpoint for handling token_hash

In order to use the updated email links we will need to setup a endpoint for verifying the token_hash along with the type to exchange token_hash for the user's session, which is set as a cookie for future requests made to Supabase.

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

app/auth/confirm/route.ts

_46
import { createServerClient, type CookieOptions } from '@supabase/ssr'
_46
import { type EmailOtpType } from '@supabase/supabase-js'
_46
import { cookies } from 'next/headers'
_46
import { NextRequest, NextResponse } from 'next/server'
_46
_46
export async function GET(request: NextRequest) {
_46
const { searchParams } = new URL(request.url)
_46
const token_hash = searchParams.get('token_hash')
_46
const type = searchParams.get('type') as EmailOtpType | null
_46
const next = searchParams.get('next') ?? '/'
_46
const redirectTo = request.nextUrl.clone()
_46
redirectTo.pathname = next
_46
_46
if (token_hash && type) {
_46
const cookieStore = cookies()
_46
const supabase = createServerClient(
_46
process.env.NEXT_PUBLIC_SUPABASE_URL!,
_46
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
_46
{
_46
cookies: {
_46
get(name: string) {
_46
return cookieStore.get(name)?.value
_46
},
_46
set(name: string, value: string, options: CookieOptions) {
_46
cookieStore.set({ name, value, ...options })
_46
},
_46
remove(name: string, options: CookieOptions) {
_46
cookieStore.delete({ name, ...options })
_46
},
_46
},
_46
}
_46
)
_46
_46
const { error } = await supabase.auth.verifyOtp({
_46
type,
_46
token_hash,
_46
})
_46
if (!error) {
_46
return NextResponse.redirect(redirectTo)
_46
}
_46
}
_46
_46
// return the user to an error page with some instructions
_46
redirectTo.pathname = '/auth/auth-code-error'
_46
return NextResponse.redirect(redirectTo)
_46
}

Update email templates with URL for API endpoint

Let's update the URL in our email templates to point to our new confirmation endpoint for the user to get confirmed.

Confirm signup template


_10
<h2>Confirm your signup</h2>
_10
_10
<p>Follow this link to confirm your user:</p>
_10
<p>
_10
<a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email"
_10
>Confirm your email</a
_10
>
_10
</p>

Invite user template


_12
<h2>You have been invited</h2>
_12
_12
<p>
_12
You have been invited to create a user on {{ .SiteURL }}. Follow this link to accept the invite:
_12
</p>
_12
_12
<p>
_12
<a
_12
href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=invite&next=/path-to-your-update-password-page"
_12
>Accept the invite</a
_12
>
_12
</p>

Magic Link template


_10
<h2>Magic Link</h2>
_10
_10
<p>Follow this link to login:</p>
_10
<p><a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=magiclink">Log In</a></p>

Change Email Address template


_10
<h2>Confirm Change of Email</h2>
_10
_10
<p>Follow this link to confirm the update of your email from {{ .Email }} to {{ .NewEmail }}:</p>
_10
<p>
_10
<a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email_change">
_10
Change Email
_10
</a>
_10
</p>

Reset Password template


_10
<h2>Reset Password</h2>
_10
_10
<p>Follow this link to reset the password for your user:</p>
_10
<p>
_10
<a
_10
href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=recovery&next=/path-to-your-update-password-page"
_10
>Reset Password</a
_10
>
_10
</p>