mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
299 lines
9.0 KiB
Plaintext
299 lines
9.0 KiB
Plaintext
For a PKCE flow, for example in Server-Side Auth, you need an extra step to handle the code exchange. When calling `signInWithOAuth`, provide a `redirectTo` URL which points to a callback route. This redirect URL should be added to your [redirect allow list](/docs/guides/auth/redirect-urls).
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="client"
|
|
queryGroup="environment"
|
|
>
|
|
<TabPanel id="client" label="Client">
|
|
|
|
In the browser, `signInWithOAuth` automatically redirects to the OAuth provider's authentication endpoint, which then redirects to your endpoint.
|
|
|
|
```js
|
|
import { createClient, type Provider } from '@supabase/supabase-js';
|
|
const supabase = createClient('https://your-project-id.supabase.co', 'sb_publishable_...')
|
|
const provider = 'provider' as Provider
|
|
|
|
// ---cut---
|
|
await supabase.auth.signInWithOAuth({
|
|
provider,
|
|
options: {
|
|
redirectTo: `http://example.com/auth/callback`,
|
|
},
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="server" label="Server">
|
|
|
|
In the server, you need to handle the redirect to the OAuth provider's authentication endpoint. The `signInWithOAuth` method returns the endpoint URL, which you can redirect to.
|
|
|
|
<Admonition type="caution">
|
|
|
|
Use a [server client](/docs/guides/auth/server-side/creating-a-client) from `@supabase/ssr`, and send its cookies with the redirect. `signInWithOAuth` stores the PKCE code verifier in a cookie. When that `Set-Cookie` header doesn't reach the browser, the callback can't exchange the code for a session.
|
|
|
|
</Admonition>
|
|
|
|
```ts
|
|
import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'
|
|
import type { Provider } from '@supabase/supabase-js'
|
|
|
|
// A route handler that receives the incoming Request
|
|
async function handler(request: Request) {
|
|
const headers = new Headers()
|
|
const provider = 'provider' as Provider
|
|
|
|
const supabase = createServerClient('https://your-project-id.supabase.co', 'sb_publishable_...', {
|
|
cookies: {
|
|
getAll() {
|
|
return parseCookieHeader(request.headers.get('Cookie') ?? '') as {
|
|
name: string
|
|
value: string
|
|
}[]
|
|
},
|
|
setAll(cookiesToSet, cacheHeaders) {
|
|
cookiesToSet.forEach(({ name, value, options }) =>
|
|
headers.append('Set-Cookie', serializeCookieHeader(name, value, options))
|
|
)
|
|
Object.entries(cacheHeaders).forEach(([key, value]) => headers.set(key, value))
|
|
},
|
|
},
|
|
})
|
|
|
|
const { data, error } = await supabase.auth.signInWithOAuth({
|
|
provider,
|
|
options: {
|
|
redirectTo: 'http://example.com/auth/callback',
|
|
},
|
|
})
|
|
|
|
if (error) {
|
|
throw error
|
|
}
|
|
|
|
// Redirect with `headers` so the browser stores the code verifier cookie.
|
|
// Frameworks usually wrap this, for example `redirect(data.url, { headers })` in Remix.
|
|
headers.set('Location', data.url)
|
|
return new Response(null, { status: 303, headers })
|
|
}
|
|
```
|
|
|
|
When your framework sets cookies on the response for you, such as `cookies()` in Next.js or `event.cookies` in SvelteKit, write to that API in `setAll` instead. The cookie is then already on the response, and the redirect needs no extra headers.
|
|
|
|
</TabPanel>
|
|
|
|
</Tabs>
|
|
|
|
At the callback endpoint, handle the code exchange to save the user session.
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="nextjs"
|
|
queryGroup="framework"
|
|
>
|
|
<TabPanel id="nextjs" label="Next.js">
|
|
|
|
Create a new file at `app/auth/callback/route.ts` and populate with the following:
|
|
|
|
```ts name=app/auth/callback/route.ts
|
|
import { 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: Request) {
|
|
const { searchParams, origin } = new URL(request.url)
|
|
const code = searchParams.get('code')
|
|
// if "next" is in param, use it as the redirect URL
|
|
let next = searchParams.get('next') ?? '/'
|
|
if (!next.startsWith('/')) {
|
|
// if "next" is not a relative URL, use the default
|
|
next = '/'
|
|
}
|
|
|
|
if (code) {
|
|
const supabase = await createClient()
|
|
const { error } = await supabase.auth.exchangeCodeForSession(code)
|
|
if (!error) {
|
|
const forwardedHost = request.headers.get('x-forwarded-host') // original origin before load balancer
|
|
const isLocalEnv = process.env.NODE_ENV === 'development'
|
|
if (isLocalEnv) {
|
|
// we can be sure that there is no load balancer in between, so no need to watch for X-Forwarded-Host
|
|
return NextResponse.redirect(`${origin}${next}`)
|
|
} else if (forwardedHost) {
|
|
return NextResponse.redirect(`https://${forwardedHost}${next}`)
|
|
} else {
|
|
return NextResponse.redirect(`${origin}${next}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
// return the user to an error page with instructions
|
|
return NextResponse.redirect(`${origin}/auth/auth-code-error`)
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="sveltekit" label="SvelteKit">
|
|
|
|
Create a new file at `src/routes/auth/callback/+server.js` and populate with the following:
|
|
|
|
```js name=src/routes/auth/callback/+server.js
|
|
import { redirect } from '@sveltejs/kit'
|
|
|
|
export const GET = async (event) => {
|
|
const {
|
|
url,
|
|
locals: { supabase },
|
|
} = event
|
|
const code = url.searchParams.get('code')
|
|
const next = url.searchParams.get('next') ?? '/'
|
|
|
|
if (code) {
|
|
const { error } = await supabase.auth.exchangeCodeForSession(code)
|
|
if (!error) {
|
|
redirect(303, `/${next.slice(1)}`)
|
|
}
|
|
}
|
|
|
|
// return the user to an error page with instructions
|
|
redirect(303, '/auth/auth-code-error')
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="astro" label="Astro">
|
|
|
|
Create a new file at `src/pages/auth/callback.ts` and populate with the following:
|
|
|
|
```ts name=src/pages/auth/callback.ts
|
|
import { createServerClient, parseCookieHeader } from '@supabase/ssr'
|
|
import { type APIRoute } from 'astro'
|
|
|
|
export const GET: APIRoute = async ({ request, cookies, redirect }) => {
|
|
const requestUrl = new URL(request.url)
|
|
const code = requestUrl.searchParams.get('code')
|
|
const next = requestUrl.searchParams.get('next') || '/'
|
|
|
|
if (code) {
|
|
const supabase = createServerClient(
|
|
import.meta.env.PUBLIC_SUPABASE_URL,
|
|
import.meta.env.PUBLIC_SUPABASE_PUBLISHABLE_KEY,
|
|
{
|
|
cookies: {
|
|
getAll() {
|
|
return parseCookieHeader(Astro.request.headers.get('Cookie') ?? '')
|
|
},
|
|
setAll(cookiesToSet, _headers) {
|
|
cookiesToSet.forEach(({ name, value, options }) =>
|
|
Astro.cookies.set(name, value, options)
|
|
)
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
const { error } = await supabase.auth.exchangeCodeForSession(code)
|
|
|
|
if (!error) {
|
|
return redirect(next)
|
|
}
|
|
}
|
|
|
|
// return the user to an error page with instructions
|
|
return redirect('/auth/auth-code-error')
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="remix" label="Remix">
|
|
|
|
Create a new file at `app/routes/auth.callback.tsx` and populate with the following:
|
|
|
|
```ts name=app/routes/auth.callback.tsx
|
|
import { redirect, type LoaderFunctionArgs } from '@remix-run/node'
|
|
import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'
|
|
|
|
export async function loader({ request }: LoaderFunctionArgs) {
|
|
const requestUrl = new URL(request.url)
|
|
const code = requestUrl.searchParams.get('code')
|
|
const next = requestUrl.searchParams.get('next') || '/'
|
|
const responseHeaders = new Headers()
|
|
|
|
if (code) {
|
|
const supabase = createServerClient(
|
|
process.env.SUPABASE_URL!,
|
|
process.env.SUPABASE_PUBLISHABLE_KEY!,
|
|
{
|
|
cookies: {
|
|
getAll() {
|
|
return parseCookieHeader(request.headers.get('Cookie') ?? '')
|
|
},
|
|
setAll(cookiesToSet, cacheHeaders) {
|
|
cookiesToSet.forEach(({ name, value, options }) =>
|
|
responseHeaders.append('Set-Cookie', serializeCookieHeader(name, value, options))
|
|
)
|
|
Object.entries(cacheHeaders).forEach(([key, value]) => responseHeaders.set(key, value))
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
const { error } = await supabase.auth.exchangeCodeForSession(code)
|
|
|
|
if (!error) {
|
|
return redirect(next, { headers: responseHeaders })
|
|
}
|
|
}
|
|
|
|
// return the user to an error page with instructions
|
|
return redirect('/auth/auth-code-error', { headers: responseHeaders })
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="express" label="Express">
|
|
|
|
Create a new route in your express app and populate with the following:
|
|
|
|
```js name=app.js
|
|
...
|
|
app.get("/auth/callback", async function (req, res) {
|
|
const code = req.query.code
|
|
const next = req.query.next ?? "/"
|
|
|
|
if (code) {
|
|
const supabase = createServerClient(
|
|
process.env.SUPABASE_URL,
|
|
process.env.SUPABASE_PUBLISHABLE_KEY, {
|
|
cookies: {
|
|
getAll() {
|
|
return parseCookieHeader(req.headers.cookie ?? '')
|
|
},
|
|
setAll(cookiesToSet, headers) {
|
|
cookiesToSet.forEach(({ name, value, options }) =>
|
|
res.appendHeader('Set-Cookie', serializeCookieHeader(name, value, options))
|
|
)
|
|
Object.entries(headers).forEach(([key, value]) =>
|
|
res.setHeader(key, value)
|
|
)
|
|
},
|
|
},
|
|
})
|
|
await supabase.auth.exchangeCodeForSession(code)
|
|
}
|
|
|
|
res.redirect(303, `/${next.slice(1)}`)
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|