Home

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

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

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=email">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>