mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 18:05:11 +03:00
update sveltekit guides with the latest ssr setup (#27641)
* update sveltekit guides with the latest ssr setup * update style formatting
This commit is contained in:
1 parent
0a1476024f
commit
8124dcae54
3 files changed
+94
-105
No files matched your search
@@ -527,8 +527,8 @@ export const load: LayoutLoad = async ({ fetch, data, depends }) => {
|
||||
fetch,
|
||||
},
|
||||
cookies: {
|
||||
get() {
|
||||
return JSON.stringify(data.session)
|
||||
getAll() {
|
||||
return data.cookies
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -553,12 +553,13 @@ export const load: LayoutLoad = async ({ fetch, data, depends }) => {
|
||||
```ts +layout.server.ts
|
||||
import type { LayoutServerLoad } from './$types'
|
||||
|
||||
export const load: LayoutServerLoad = async ({ locals: { safeGetSession } }) => {
|
||||
export const load: LayoutServerLoad = async ({ locals: { safeGetSession }, cookies }) => {
|
||||
const { session, user } = await safeGetSession()
|
||||
|
||||
return {
|
||||
session,
|
||||
user,
|
||||
cookies: cookies.getAll(),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -78,17 +78,16 @@ const supabase: Handle = async ({ event, resolve }) => {
|
||||
*/
|
||||
event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
|
||||
cookies: {
|
||||
get: (key) => event.cookies.get(key),
|
||||
getAll: () => event.cookies.getAll(),
|
||||
/**
|
||||
* SvelteKit's cookies API requires `path` to be explicitly set in
|
||||
* the cookie options. Setting `path` to `/` replicates previous/
|
||||
* standard behavior.
|
||||
*/
|
||||
set: (key, value, options) => {
|
||||
event.cookies.set(key, value, { ...options, path: '/' })
|
||||
},
|
||||
remove: (key, options) => {
|
||||
event.cookies.delete(key, { ...options, path: '/' })
|
||||
setAll: (cookiesToSet) => {
|
||||
cookiesToSet.forEach(({ name, value, options }) => {
|
||||
event.cookies.set(name, value, { ...options, path: '/' })
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -174,7 +173,9 @@ declare global {
|
||||
session: Session | null
|
||||
user: User | null
|
||||
}
|
||||
// interface PageData {}
|
||||
interface PageData {
|
||||
session: Session | null
|
||||
}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
@@ -200,10 +201,8 @@ Create a Supabase client in your root `+layout.ts`. This client can be used to a
|
||||
<CH.Code>
|
||||
|
||||
```ts src/routes/+layout.ts
|
||||
import { createBrowserClient, createServerClient, isBrowser, parse } from '@supabase/ssr'
|
||||
|
||||
import { createBrowserClient, createServerClient, isBrowser } from '@supabase/ssr'
|
||||
import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'
|
||||
|
||||
import type { LayoutLoad } from './$types'
|
||||
|
||||
export const load: LayoutLoad = async ({ data, depends, fetch }) => {
|
||||
@@ -218,20 +217,14 @@ export const load: LayoutLoad = async ({ data, depends, fetch }) => {
|
||||
global: {
|
||||
fetch,
|
||||
},
|
||||
cookies: {
|
||||
get(key) {
|
||||
const cookie = parse(document.cookie)
|
||||
return cookie[key]
|
||||
},
|
||||
},
|
||||
})
|
||||
: createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
|
||||
global: {
|
||||
fetch,
|
||||
},
|
||||
cookies: {
|
||||
get() {
|
||||
return JSON.stringify(data.session)
|
||||
getAll() {
|
||||
return data.cookies
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -256,9 +249,10 @@ export const load: LayoutLoad = async ({ data, depends, fetch }) => {
|
||||
```ts src/routes/+layout.server.ts
|
||||
import type { LayoutServerLoad } from './$types'
|
||||
|
||||
export const load: LayoutServerLoad = async ({ locals: { session } }) => {
|
||||
export const load: LayoutServerLoad = async ({ locals: { session }, cookies }) => {
|
||||
return {
|
||||
session,
|
||||
cookies: cookies.getAll(),
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -281,7 +275,7 @@ Set up a listener for Auth events on the client, to handle session refreshes and
|
||||
|
||||
```svelte src/routes/+layout.svelte
|
||||
<script>
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let data;
|
||||
@@ -289,15 +283,6 @@ Set up a listener for Auth events on the client, to handle session refreshes and
|
||||
|
||||
onMount(() => {
|
||||
const { data } = supabase.auth.onAuthStateChange((_, newSession) => {
|
||||
if (!newSession) {
|
||||
/**
|
||||
* Queue this as a task so the navigation won't prevent the
|
||||
* triggering function from completing
|
||||
*/
|
||||
setTimeout(() => {
|
||||
goto('/', { invalidateAll: true });
|
||||
});
|
||||
}
|
||||
if (newSession?.expires_at !== session?.expires_at) {
|
||||
invalidate('supabase:auth');
|
||||
}
|
||||
@@ -367,7 +352,7 @@ If you have email confirmation turned on (the default), a new user will receive
|
||||
|
||||
Change the email template to support a server-side authentication flow.
|
||||
|
||||
Go to the [Auth templates](https://supabase.com/dashboard/project/_/auth/templates) page in your dashboard. In the `Confirm signup` template, change `{{ .ConfirmationURL }}` to `{{ .SiteURL }}/api/auth/confirm?token_hash={{ .TokenHash }}&type=signup`.
|
||||
Go to the [Auth templates](https://supabase.com/dashboard/project/_/auth/templates) page in your dashboard. In the `Confirm signup` template, change `{{ .ConfirmationURL }}` to `{{ .SiteURL }}/api/auth/confirm?token_hash={{ .TokenHash }}&type=email`.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
|
||||
@@ -68,18 +68,16 @@ import type { Handle } from '@sveltejs/kit'
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
|
||||
cookies: {
|
||||
get: (key) => event.cookies.get(key),
|
||||
getAll: () => event.cookies.getAll(),
|
||||
/**
|
||||
* Note: You have to add the `path` variable to the
|
||||
* set and remove method due to sveltekit's cookie API
|
||||
* requiring this to be set, setting the path to `/`
|
||||
* will replicate previous/standard behaviour (https://kit.svelte.dev/docs/types#public-types-cookies)
|
||||
* SvelteKit's cookies API requires `path` to be explicitly set in
|
||||
* the cookie options. Setting `path` to `/` replicates previous/
|
||||
* standard behavior.
|
||||
*/
|
||||
set: (key, value, options) => {
|
||||
event.cookies.set(key, value, { ...options, path: '/' })
|
||||
},
|
||||
remove: (key, options) => {
|
||||
event.cookies.delete(key, { ...options, path: '/' })
|
||||
setAll: (cookiesToSet) => {
|
||||
cookiesToSet.forEach(({ name, value, options }) => {
|
||||
event.cookies.set(name, value, { ...options, path: '/' })
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -148,14 +146,15 @@ Create a new `src/routes/+layout.server.ts` file to handle the session on the se
|
||||
// src/routes/+layout.server.ts
|
||||
import type { LayoutServerLoad } from './$types'
|
||||
|
||||
export const load = (async ({ locals: { safeGetSession } }) => {
|
||||
export const load: LayoutServerLoad = async ({ locals: { safeGetSession }, cookies }) => {
|
||||
const { session, user } = await safeGetSession()
|
||||
|
||||
return {
|
||||
session,
|
||||
user,
|
||||
cookies: cookies.getAll(),
|
||||
}
|
||||
}) satisfies LayoutServerLoad
|
||||
}
|
||||
```
|
||||
|
||||
<Admonition type="tip">
|
||||
@@ -168,28 +167,29 @@ Create a new `src/routes/+layout.ts` file to handle the session and the supabase
|
||||
|
||||
```ts src/routes/+layout.ts
|
||||
// src/routes/+layout.ts
|
||||
import { createBrowserClient, createServerClient, isBrowser } from '@supabase/ssr'
|
||||
import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'
|
||||
import type { LayoutLoad } from './$types'
|
||||
import { createBrowserClient, isBrowser, parse } from '@supabase/ssr'
|
||||
|
||||
export const load = (async ({ fetch, data, depends }) => {
|
||||
export const load: LayoutLoad = async ({ fetch, data, depends }) => {
|
||||
depends('supabase:auth')
|
||||
|
||||
const supabase = createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
|
||||
global: {
|
||||
fetch,
|
||||
},
|
||||
cookies: {
|
||||
get(key) {
|
||||
if (!isBrowser()) {
|
||||
return JSON.stringify(data.session)
|
||||
}
|
||||
|
||||
const cookie = parse(document.cookie)
|
||||
return cookie[key]
|
||||
},
|
||||
},
|
||||
})
|
||||
const supabase = isBrowser()
|
||||
? createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
|
||||
global: {
|
||||
fetch,
|
||||
},
|
||||
})
|
||||
: createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
|
||||
global: {
|
||||
fetch,
|
||||
},
|
||||
cookies: {
|
||||
getAll() {
|
||||
return data.cookies
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* It's fine to use `getSession` here, because on the client, `getSession` is
|
||||
@@ -201,7 +201,7 @@ export const load = (async ({ fetch, data, depends }) => {
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
return { supabase, session }
|
||||
}) satisfies LayoutLoad
|
||||
}
|
||||
```
|
||||
|
||||
Update your `src/routes/+layout.svelte`:
|
||||
@@ -219,8 +219,8 @@ Update your `src/routes/+layout.svelte`:
|
||||
$: ({ supabase, session } = data)
|
||||
|
||||
onMount(() => {
|
||||
const { data } = supabase.auth.onAuthStateChange((event, _session) => {
|
||||
if (_session?.expires_at !== session?.expires_at) {
|
||||
const { data } = supabase.auth.onAuthStateChange((event, newSession) => {
|
||||
if (newSession?.expires_at !== session?.expires_at) {
|
||||
invalidate('supabase:auth')
|
||||
}
|
||||
})
|
||||
@@ -270,7 +270,6 @@ Add the `Auth` component to your home page
|
||||
<Auth
|
||||
supabaseClient={data.supabase}
|
||||
view="magic_link"
|
||||
redirectTo={`${data.url}/auth/callback`}
|
||||
showLinks={false}
|
||||
appearance={{ theme: ThemeSupa, style: { input: 'color: #fff' } }}
|
||||
/>
|
||||
@@ -297,60 +296,64 @@ export const load: PageServerLoad = async ({ url, locals: { safeGetSession } })
|
||||
}
|
||||
```
|
||||
|
||||
### Proof Key for Code Exchange (PKCE)
|
||||
### Email template
|
||||
|
||||
As we are employing Proof Key for Code Exchange (PKCE) in our authentication flow, it is necessary to create a server endpoint responsible for exchanging the code for a session.
|
||||
Change the email template to support a server-side authentication flow.
|
||||
|
||||
Go to the [Auth templates](https://supabase.com/dashboard/project/_/auth/templates) page in your dashboard. In the `Confirm signup` template, change `{{ .ConfirmationURL }}` to `{{ .SiteURL }}/api/auth/confirm?token_hash={{ .TokenHash }}&type=email`.
|
||||
|
||||
### Confirmation endpoint
|
||||
|
||||
As we are working in a server-side rendering (SSR) environment, it is necessary to create a server endpoint responsible for exchanging the `token_hash` for a session.
|
||||
|
||||
In the following code snippet, we perform the following steps:
|
||||
|
||||
- Retrieve the code sent back from the Supabase Auth server using the `code` query parameter.
|
||||
- Exchange this code for a session, which we store in our chosen storage mechanism (in this case, cookies).
|
||||
- Finally, we redirect the user to the `account` page.
|
||||
- Retrieve the `token_hash` sent back from the Supabase Auth server using the `token_hash` query parameter.
|
||||
- Exchange this `token_hash` for a session, which we store in storage (in this case, cookies).
|
||||
- Finally, the user is redirected to the `account` page or the `error` page.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
queryGroup="language"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```js title=src/routes/auth/callback/+server.js
|
||||
// src/routes/auth/callback/+server.js
|
||||
```ts title=src/routes/api/auth/confirm/+server.ts
|
||||
// src/routes/api/auth/confirm/+server.js
|
||||
import type { EmailOtpType } from '@supabase/supabase-js'
|
||||
import { redirect } from '@sveltejs/kit'
|
||||
|
||||
export const GET = async ({ url, locals: { supabase } }) => {
|
||||
const code = url.searchParams.get('code')
|
||||
|
||||
if (code) {
|
||||
await supabase.auth.exchangeCodeForSession(code)
|
||||
}
|
||||
|
||||
redirect(303, '/account')
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
```ts title=src/routes/auth/callback/+server.ts
|
||||
// src/routes/auth/callback/+server.ts
|
||||
import { redirect, type RequestHandler } from '@sveltejs/kit'
|
||||
import type { RequestHandler } from './$types'
|
||||
|
||||
export const GET: RequestHandler = async ({ url, locals: { supabase } }) => {
|
||||
const code = url.searchParams.get('code')
|
||||
const token_hash = url.searchParams.get('token_hash')
|
||||
const type = url.searchParams.get('type') as EmailOtpType | null
|
||||
const next = url.searchParams.get('next') ?? '/account'
|
||||
|
||||
if (code) {
|
||||
await supabase.auth.exchangeCodeForSession(code)
|
||||
/**
|
||||
* 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({ type, token_hash })
|
||||
if (!error) {
|
||||
redirectTo.searchParams.delete('next')
|
||||
return redirect(303, redirectTo)
|
||||
}
|
||||
}
|
||||
|
||||
redirect(303, '/account')
|
||||
redirectTo.pathname = '/auth/error'
|
||||
return redirect(303, redirectTo)
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
### Authentication error page
|
||||
|
||||
If there is an error with confirming the token you will be redirect to this error page.
|
||||
|
||||
```svelte src/routes/auth/error/+page.svelte
|
||||
<p>Login error</p>
|
||||
```
|
||||
|
||||
### Account page
|
||||
|
||||
|
||||
Reference in new issue
Block a user