The PKCE flow allows for server-side authentication. Unlike the implicit flow, which directly provides your app with the access token after the user clicks the confirmation link, the PKCE flow requires an intermediate token exchange step before you can get the access token.
##### Step 1: Update reset password email
Update your reset password email template to send the token hash. See [Email Templates](/docs/guides/auth/auth-email-templates) for how to configure your email templates.
Your reset password email template should contain the following HTML:
```html
Reset Password
Follow this link to reset the password for your user:
Reset Password
```
##### Step 2: Create token exchange endpoint
Create an API endpoint at `/auth/confirm` to handle the token exchange.
<$Partial path="create_client_snippet.mdx" />
Create a new file at `app/auth/confirm/route.ts` and populate with the following:
```ts app/auth/confirm/route.ts
import { type EmailOtpType } from '@supabase/supabase-js'
import { cookies } from 'next/headers'
import { NextRequest, NextResponse } from 'next/server'
// The client you created from the Server-Side Auth instructions
import { createClient } from '@/utils/supabase/server'
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const token_hash = searchParams.get('token_hash')
const type = searchParams.get('type') as EmailOtpType | null
const next = searchParams.get('next') ?? '/'
const redirectTo = request.nextUrl.clone()
redirectTo.pathname = next
if (token_hash && type) {
const supabase = await createClient()
const { error } = await supabase.auth.verifyOtp({
type,
token_hash,
})
if (!error) {
return NextResponse.redirect(redirectTo)
}
}
// return the user to an error page with some instructions
redirectTo.pathname = '/auth/auth-code-error'
return NextResponse.redirect(redirectTo)
}
```
Create a new file at `src/routes/auth/confirm/+server.ts` and populate with the following:
```ts src/routes/auth/confirm/+server.ts
import { redirect } from '@sveltejs/kit'
import { type EmailOtpType } from '@supabase/supabase-js'
export const GET = async (event) => {
const {
url,
locals: { supabase },
} = event
const token_hash = url.searchParams.get('token_hash') as string
const type = url.searchParams.get('type') as EmailOtpType | null
const next = url.searchParams.get('next') ?? '/'
/**
* Clean up the redirect URL by deleting the Auth flow parameters.
*
* `next` is preserved for now, because it's needed in the error case.
*/
const redirectTo = new URL(url)
redirectTo.pathname = next
redirectTo.searchParams.delete('token_hash')
redirectTo.searchParams.delete('type')
if (token_hash && type) {
const { error } = await supabase.auth.verifyOtp({ token_hash, type })
if (!error) {
redirectTo.searchParams.delete('next')
redirect(303, redirectTo)
}
}
// return the user to an error page with some instructions
redirectTo.pathname = '/auth/error'
redirect(303, redirectTo)
}
```
Create a new file at `src/pages/auth/confirm.ts` and populate with the following:
```ts src/pages/auth/confirm.ts
import { createServerClient, parseCookieHeader } from '@supabase/ssr'
import { type EmailOtpType } from '@supabase/supabase-js'
import { type APIRoute } from 'astro'
export const GET: APIRoute = async ({ request, cookies, redirect }) => {
const requestUrl = new URL(request.url)
const token_hash = requestUrl.searchParams.get('token_hash')
const type = requestUrl.searchParams.get('type') as EmailOtpType | null
const next = requestUrl.searchParams.get('next') || '/'
if (token_hash && type) {
const supabase = createServerClient(
import.meta.env.PUBLIC_SUPABASE_URL,
import.meta.env.PUBLIC_SUPABASE_PUBLISHABLE_KEY,
{
cookies: {
getAll() {
return parseCookieHeader(request.headers.get('Cookie') ?? '')
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) => cookies.set(name, value, options))
},
},
}
)
const { error } = await supabase.auth.verifyOtp({
type,
token_hash,
})
if (!error) {
return redirect(next)
}
}
// return the user to an error page with some instructions
return redirect('/auth/auth-code-error')
}
```
Create a new file at `app/routes/auth.confirm.tsx` and populate with the following:
```ts app/routes/auth.confirm.tsx
import { redirect, type LoaderFunctionArgs } from '@remix-run/node'
import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'
import { type EmailOtpType } from '@supabase/supabase-js'
export async function loader({ request }: LoaderFunctionArgs) {
const requestUrl = new URL(request.url)
const token_hash = requestUrl.searchParams.get('token_hash')
const type = requestUrl.searchParams.get('type') as EmailOtpType | null
const next = requestUrl.searchParams.get('next') || '/'
const headers = new Headers()
if (token_hash && type) {
const supabase = createServerClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_PUBLISHABLE_KEY!,
{
cookies: {
getAll() {
return parseCookieHeader(request.headers.get('Cookie') ?? '')
},
setAll(key, value, options) {
headers.append('Set-Cookie', serializeCookieHeader(key, value, options))
},
},
}
)
const { error } = await supabase.auth.verifyOtp({
type,
token_hash,
})
if (!error) {
return redirect(next, { headers })
}
}
// return the user to an error page with instructions
return redirect('/auth/auth-code-error', { headers })
}
```
Create a new route in your express app and populate with the following:
```js app.js
// The client you created from the Server-Side Auth instructions
const { createClient } = require("./lib/supabase")
...
app.get("/auth/confirm", async function (req, res) {
const token_hash = req.query.token_hash
const type = req.query.type
const next = req.query.next ?? "/"
if (token_hash && type) {
const supabase = createClient({ req, res })
const { error } = await supabase.auth.verifyOtp({
type,
token_hash,
})
if (!error) {
res.redirect(303, `/${next.slice(1)}`)
}
}
// return the user to an error page with some instructions
res.redirect(303, '/auth/auth-code-error')
})
```
##### Step 3: Call the reset password by email function to initiate the flow
```js
async function resetPassword() {
const { data, error } = await supabase.auth.resetPasswordForEmail(email)
}
```
<$Show if="sdk:swift">
```swift
try await supabase.auth.resetPasswordForEmail("valid.email@supabase.io")
```
$Show>
<$Show if="sdk:kotlin">
```kotlin
supabase.gotrue.sendRecoveryEmail(
email = "valid.email@supabase.io",
)
```
$Show>
<$Show if="sdk:python">
```python
supabase.auth.reset_password_email('valid.email@supabase.io')
```
$Show>
<$Show if="sdk:dart">
```dart
await supabase.auth.resetPasswordForEmail('valid.email@supabase.io');
```
$Show>
Once you have a session, collect the user's new password and call `updateUser` to update their password.