* fix: rewrite relative URLs when syncing to GitHub discussion Relative URLs back to supabse.com won't work in GitHub discussions, so rewrite them back to absolute URLs starting with https://supabase.com * fix: replace all supabase urls with relative urls * chore: add linting for relative urls * chore: bump linter version * Prettier --------- Co-authored-by: Chris Chinchilla <chris.ward@supabase.io>
2052 lines
51 KiB
Plaintext
2052 lines
51 KiB
Plaintext
---
|
|
id: 'sveltekit'
|
|
title: 'Supabase Auth with SvelteKit'
|
|
description: 'Convenience helpers for implementing user authentication in SvelteKit.'
|
|
sidebar_label: 'SvelteKit'
|
|
sitemapPriority: 0.5
|
|
---
|
|
|
|
<Admonition type="caution">
|
|
|
|
We generally recommend using the new `@supabase/ssr` package instead of `auth-helpers`. `@supabase/ssr` takes the core concepts of the Auth Helpers package and makes them available to any server framework. Check out the [migration doc](/docs/guides/auth/server-side/migrating-to-ssr-from-auth-helpers) to learn more.
|
|
|
|
</Admonition>
|
|
|
|
<Accordion
|
|
type="default"
|
|
openBehaviour="multiple"
|
|
chevronAlign="right"
|
|
justified
|
|
size="medium"
|
|
className="text-foreground-light border-b mt-8 pb-2"
|
|
>
|
|
|
|
<AccordionItem
|
|
header="See legacy docs"
|
|
id="legacy-docs"
|
|
>
|
|
|
|
This submodule provides convenience helpers for implementing user authentication in [SvelteKit](https://kit.svelte.dev/) applications.
|
|
|
|
## Configuration
|
|
|
|
### Install SvelteKit Auth helpers library
|
|
|
|
This library supports Node.js `^16.15.0`.
|
|
|
|
```sh Terminal
|
|
npm install @supabase/auth-helpers-sveltekit @supabase/supabase-js
|
|
```
|
|
|
|
### Declare environment variables
|
|
|
|
Retrieve your project's URL and anon key from your [API settings](/dashboard/project/_/settings/api), and create a `.env.local` file with the following environment variables:
|
|
|
|
```bash .env.local
|
|
# Find these in your Supabase project settings https://supabase.com/dashboard/project/_/settings/api
|
|
PUBLIC_SUPABASE_URL=https://your-project.supabase.co
|
|
PUBLIC_SUPABASE_PUBLISHABLE_KEY=sb_publishable_... or anon key
|
|
```
|
|
|
|
### Creating a Supabase client
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="js"
|
|
queryGroup="language"
|
|
>
|
|
<TabPanel id="js" label="JavaScript">
|
|
|
|
Create a new `hooks.server.js` file in the root of your project and populate with the following to retrieve the user session.
|
|
|
|
<$Partial path="get_session_warning.mdx" />
|
|
|
|
```js src/hooks.server.js
|
|
// src/hooks.server.js
|
|
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public'
|
|
import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit'
|
|
|
|
export const handle = async ({ event, resolve }) => {
|
|
event.locals.supabase = createSupabaseServerClient({
|
|
supabaseUrl: PUBLIC_SUPABASE_URL,
|
|
supabaseKey: PUBLIC_SUPABASE_PUBLISHABLE_KEY,
|
|
event,
|
|
})
|
|
|
|
/**
|
|
* Unlike `supabase.auth.getSession`, which is unsafe on the server because it
|
|
* doesn't validate the JWT, this function validates the JWT by first calling
|
|
* `getUser` and aborts early if the JWT signature is invalid.
|
|
*/
|
|
event.locals.safeGetSession = async () => {
|
|
const {
|
|
data: { user },
|
|
error,
|
|
} = await supabase.auth.getUser()
|
|
if (error) {
|
|
return { session: null, user: null }
|
|
}
|
|
|
|
const {
|
|
data: { session },
|
|
} = await event.locals.supabase.auth.getSession()
|
|
return { session, user }
|
|
}
|
|
|
|
return resolve(event, {
|
|
filterSerializedResponseHeaders(name) {
|
|
return name === 'content-range' || name === 'x-supabase-api-version'
|
|
},
|
|
})
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="ts" label="TypeScript">
|
|
|
|
Create a new `hooks.server.ts` file in the root of your project and populate with the following:
|
|
|
|
```ts src/hooks.server.ts
|
|
// src/hooks.server.ts
|
|
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public'
|
|
import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit'
|
|
import type { Handle } from '@sveltejs/kit'
|
|
|
|
export const handle: Handle = async ({ event, resolve }) => {
|
|
event.locals.supabase = createSupabaseServerClient({
|
|
supabaseUrl: PUBLIC_SUPABASE_URL,
|
|
supabaseKey: PUBLIC_SUPABASE_PUBLISHABLE_KEY,
|
|
event,
|
|
})
|
|
|
|
/**
|
|
* Unlike `supabase.auth.getSession`, which is unsafe on the server because it
|
|
* doesn't validate the JWT, this function validates the JWT by first calling
|
|
* `getUser` and aborts early if the JWT signature is invalid.
|
|
*/
|
|
event.locals.safeGetSession = async () => {
|
|
const {
|
|
data: { user },
|
|
error,
|
|
} = await supabase.auth.getUser()
|
|
if (error) {
|
|
return { session: null, user: null }
|
|
}
|
|
|
|
const {
|
|
data: { session },
|
|
} = await event.locals.supabase.auth.getSession()
|
|
return { session, user }
|
|
}
|
|
|
|
return resolve(event, {
|
|
filterSerializedResponseHeaders(name) {
|
|
return name === 'content-range' || name === 'x-supabase-api-version'
|
|
},
|
|
})
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
<Admonition type="note">
|
|
|
|
Note that we are specifying `filterSerializedResponseHeaders` here. We need to tell SvelteKit that Supabase needs the `content-range` and `x-supabase-api-version` headers.
|
|
|
|
</Admonition>
|
|
|
|
### Code Exchange route
|
|
|
|
The `Code Exchange` route is required for the [server-side auth flow](/docs/guides/auth/server-side-rendering) implemented by the SvelteKit Auth Helpers. It exchanges an auth `code` for the user's `session`, which is set as a cookie for future requests made to Supabase.
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="js"
|
|
queryGroup="language"
|
|
>
|
|
<TabPanel id="js" label="JavaScript">
|
|
|
|
Create a new file at `src/routes/auth/callback/+server.js` and populate with the following:
|
|
|
|
```js src/routes/auth/callback/+server.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, '/')
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="ts" label="TypeScript">
|
|
|
|
Create a new file at `src/routes/auth/callback/+server.ts` and populate with the following:
|
|
|
|
```ts src/routes/auth/callback/+server.ts
|
|
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, '/')
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
### Generate types from your database
|
|
|
|
In order to get the most out of TypeScript and its IntelliSense, you should import the generated Database types into the `app.d.ts` type definition file that comes with your SvelteKit project, where `import('./DatabaseDefinitions')` points to the generated types file outlined in [v2 docs here](/docs/reference/javascript/release-notes#typescript-support) after you have logged in, linked, and generated types through the Supabase CLI.
|
|
|
|
```ts src/app.d.ts
|
|
// src/app.d.ts
|
|
|
|
import { SupabaseClient, Session, User } from '@supabase/supabase-js'
|
|
import { Database } from './DatabaseDefinitions'
|
|
|
|
declare global {
|
|
namespace App {
|
|
interface Locals {
|
|
supabase: SupabaseClient<Database>
|
|
safeGetSession(): Promise<{ session: Session | null; user: User | null }>
|
|
}
|
|
interface PageData {
|
|
session: Session | null
|
|
user: User | null
|
|
}
|
|
// interface Error {}
|
|
// interface Platform {}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Authentication
|
|
|
|
Authentication can be initiated [client](/docs/guides/auth/auth-helpers/sveltekit#client-side) or [server-side](/docs/guides/auth/auth-helpers/sveltekit#server-side). All of the [supabase-js authentication strategies](/docs/reference/javascript/auth-api) are supported with the Auth Helpers client.
|
|
|
|
<Admonition type="note">
|
|
|
|
Note: The authentication flow requires the [Code Exchange Route](/docs/guides/auth/auth-helpers/sveltekit#code-exchange-route) to exchange a `code` for the user's `session`.
|
|
|
|
</Admonition>
|
|
|
|
### Client-side
|
|
|
|
#### Send session to client
|
|
|
|
To make the session available across the UI, including pages and layouts, it is crucial to pass the session as a parameter in the root layout's server load function.
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="js"
|
|
queryGroup="language"
|
|
>
|
|
<TabPanel id="js" label="JavaScript">
|
|
|
|
```js src/routes/+layout.server.js
|
|
// src/routes/+layout.server.js
|
|
export const load = async ({ locals: { safeGetSession } }) => {
|
|
const { session, user } = await safeGetSession()
|
|
|
|
return {
|
|
session,
|
|
user,
|
|
}
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="ts" label="TypeScript">
|
|
|
|
```ts src/routes/+layout.server.ts
|
|
// src/routes/+layout.server.ts
|
|
export const load = async ({ locals: { safeGetSession } }) => {
|
|
const { session, user } = await safeGetSession()
|
|
|
|
return {
|
|
session,
|
|
user,
|
|
}
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
#### Shared load functions and pages
|
|
|
|
To utilize Supabase in shared load functions and within pages, it is essential to create a Supabase client in the root layout load.
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="js"
|
|
queryGroup="language"
|
|
>
|
|
<TabPanel id="js" label="JavaScript">
|
|
|
|
```ts src/routes/+layout.js
|
|
// src/routes/+layout.js
|
|
import { PUBLIC_SUPABASE_PUBLISHABLE_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'
|
|
import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit'
|
|
|
|
export const load = async ({ fetch, data, depends }) => {
|
|
depends('supabase:auth')
|
|
|
|
const supabase = createSupabaseLoadClient({
|
|
supabaseUrl: PUBLIC_SUPABASE_URL,
|
|
supabaseKey: PUBLIC_SUPABASE_PUBLISHABLE_KEY,
|
|
event: { fetch },
|
|
serverSession: data.session,
|
|
})
|
|
|
|
/**
|
|
* It's fine to use `getSession` here, because on the client, `getSession` is
|
|
* safe, and on the server, it reads `session` from the `LayoutData`, which
|
|
* safely checked the session using `safeGetSession`.
|
|
*/
|
|
const {
|
|
data: { session },
|
|
} = await supabase.auth.getSession()
|
|
|
|
return { supabase, session }
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="ts" label="TypeScript">
|
|
|
|
```ts src/routes/+layout.ts
|
|
// src/routes/+layout.ts
|
|
import { PUBLIC_SUPABASE_PUBLISHABLE_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'
|
|
import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit'
|
|
import type { Database } from '../DatabaseDefinitions'
|
|
|
|
export const load = async ({ fetch, data, depends }) => {
|
|
depends('supabase:auth')
|
|
|
|
const supabase = createSupabaseLoadClient<Database>({
|
|
supabaseUrl: PUBLIC_SUPABASE_URL,
|
|
supabaseKey: PUBLIC_SUPABASE_PUBLISHABLE_KEY,
|
|
event: { fetch },
|
|
serverSession: data.session,
|
|
})
|
|
|
|
/**
|
|
* It's fine to use `getSession` here, because on the client, `getSession` is
|
|
* safe, and on the server, it reads `session` from the `LayoutData`, which
|
|
* safely checked the session using `safeGetSession`.
|
|
*/
|
|
const {
|
|
data: { session },
|
|
} = await supabase.auth.getSession()
|
|
|
|
return { supabase, session }
|
|
}
|
|
```
|
|
|
|
<Admonition type="note">
|
|
|
|
TypeScript types can be [generated with the Supabase CLI](/docs/reference/javascript/typescript-support) and passed to `createSupabaseLoadClient` to add type support to the Supabase client.
|
|
|
|
</Admonition>
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
Access the client inside pages by `$page.data.supabase` or `data.supabase` when using `export let data`.
|
|
|
|
The usage of `depends` tells SvelteKit that this load function should be executed whenever `invalidate` is called to keep the page store in sync.
|
|
|
|
`createSupabaseLoadClient` caches the client when running in a browser environment and therefore does not create a new client for every time the load function runs.
|
|
|
|
#### Setting up the event listener on the client side
|
|
|
|
We need to create an event listener in the root `+layout.svelte` file in order to catch Supabase events being triggered.
|
|
|
|
```svelte src/routes/+layout.svelte
|
|
<!-- src/routes/+layout.svelte -->
|
|
<script lang="ts">
|
|
import { invalidate } from '$app/navigation'
|
|
import { onMount } from 'svelte'
|
|
|
|
export let data
|
|
|
|
let { supabase, session } = data
|
|
$: ({ supabase, session } = data)
|
|
|
|
onMount(() => {
|
|
const {
|
|
data: { subscription },
|
|
} = supabase.auth.onAuthStateChange((event, _session) => {
|
|
if (_session?.expires_at !== session?.expires_at) {
|
|
invalidate('supabase:auth')
|
|
}
|
|
})
|
|
|
|
return () => subscription.unsubscribe()
|
|
});
|
|
</script>
|
|
|
|
<slot />
|
|
```
|
|
|
|
The usage of `invalidate` tells SvelteKit that the root `+layout.ts` load function should be executed whenever the session updates to keep the page store in sync.
|
|
|
|
#### Sign in / sign up / sign out
|
|
|
|
We can access the Supabase instance in our `+page.svelte` file through the data object.
|
|
|
|
```svelte src/routes/auth/+page.svelte
|
|
<!-- // src/routes/auth/+page.svelte -->
|
|
<script>
|
|
export let data
|
|
let { supabase } = data
|
|
$: ({ supabase } = data)
|
|
|
|
let email
|
|
let password
|
|
|
|
const handleSignUp = async () => {
|
|
await supabase.auth.signUp({
|
|
email,
|
|
password,
|
|
options: {
|
|
emailRedirectTo: `${location.origin}/auth/callback`,
|
|
},
|
|
})
|
|
}
|
|
|
|
const handleSignIn = async () => {
|
|
await supabase.auth.signInWithPassword({
|
|
email,
|
|
password,
|
|
})
|
|
}
|
|
|
|
const handleSignOut = async () => {
|
|
await supabase.auth.signOut()
|
|
}
|
|
</script>
|
|
|
|
<form on:submit="{handleSignUp}">
|
|
<input name="email" bind:value="{email}" />
|
|
<input type="password" name="password" bind:value="{password}" />
|
|
<button>Sign up</button>
|
|
</form>
|
|
|
|
<button on:click="{handleSignIn}">Sign in</button>
|
|
<button on:click="{handleSignOut}">Sign out</button>
|
|
```
|
|
|
|
### Server-side
|
|
|
|
[Form Actions](https://kit.svelte.dev/docs/form-actions) can be used to trigger the authentication process from form submissions.
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="js"
|
|
queryGroup="language"
|
|
>
|
|
<TabPanel id="js" label="JavaScript">
|
|
|
|
```js src/routes/login/+page.server.js
|
|
// src/routes/login/+page.server.js
|
|
import { fail } from '@sveltejs/kit'
|
|
|
|
export const actions = {
|
|
default: async ({ request, url, locals: { supabase } }) => {
|
|
const formData = await request.formData()
|
|
const email = formData.get('email')
|
|
const password = formData.get('password')
|
|
|
|
const { error } = await supabase.auth.signUp({
|
|
email,
|
|
password,
|
|
options: {
|
|
emailRedirectTo: `${url.origin}/auth/callback`,
|
|
},
|
|
})
|
|
|
|
if (error) {
|
|
return fail(500, { message: 'Server error. Try again later.', success: false, email })
|
|
}
|
|
|
|
return {
|
|
message: 'Please check your email for a magic link to log into the website.',
|
|
success: true,
|
|
}
|
|
},
|
|
}
|
|
```
|
|
|
|
```svelte src/routes/login/+page.svelte
|
|
<!-- // src/routes/login/+page.svelte -->
|
|
<script>
|
|
import { enhance } from '$app/forms'
|
|
export let form
|
|
</script>
|
|
|
|
<form method="post" use:enhance>
|
|
<input name="email" value={form?.email ?? ''} />
|
|
<input type="password" name="password" />
|
|
<button>Sign up</button>
|
|
</form>
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="ts" label="TypeScript">
|
|
|
|
```js src/routes/login/+page.server.ts
|
|
// src/routes/login/+page.server.ts
|
|
import { fail } from '@sveltejs/kit'
|
|
|
|
export const actions = {
|
|
default: async ({ request, url, locals: { supabase } }) => {
|
|
const formData = await request.formData()
|
|
const email = formData.get('email') as string
|
|
const password = formData.get('password') as string
|
|
|
|
const { error } = await supabase.auth.signUp({
|
|
email,
|
|
password,
|
|
options: {
|
|
emailRedirectTo: `${url.origin}/auth/callback`,
|
|
},
|
|
})
|
|
|
|
if (error) {
|
|
return fail(500, { message: 'Server error. Try again later.', success: false, email })
|
|
}
|
|
|
|
return {
|
|
message: 'Please check your email for a magic link to log into the website.',
|
|
success: true,
|
|
}
|
|
},
|
|
}
|
|
```
|
|
|
|
```svelte src/routes/login/+page.svelte
|
|
<!-- // src/routes/login/+page.svelte -->
|
|
<script lang="ts">
|
|
import { enhance } from '$app/forms'
|
|
export let form
|
|
</script>
|
|
|
|
<form method="post" use:enhance>
|
|
<input name="email" value={form?.email ?? ''} />
|
|
<input type="password" name="password" />
|
|
<button>Sign up</button>
|
|
</form>
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
## Authorization
|
|
|
|
### Protecting API routes
|
|
|
|
Wrap an API Route to check that the user has a valid session. If they're not logged in the session is `null`.
|
|
|
|
```ts src/routes/api/protected-route/+server.ts
|
|
// src/routes/api/protected-route/+server.ts
|
|
import { json, error } from '@sveltejs/kit'
|
|
|
|
export const GET = async ({ locals: { supabase, safeGetSession } }) => {
|
|
const { session } = await safeGetSession()
|
|
if (!session) {
|
|
// the user is not signed in
|
|
throw error(401, { message: 'Unauthorized' })
|
|
}
|
|
const { data } = await supabase.from('test').select('*')
|
|
|
|
return json({ data })
|
|
}
|
|
```
|
|
|
|
If you visit `/api/protected-route` without a valid session cookie, you will get a 401 response.
|
|
|
|
### Protecting actions
|
|
|
|
Wrap an Action to check that the user has a valid session. If they're not logged in the session is `null`.
|
|
|
|
```ts src/routes/posts/+page.server.ts
|
|
// src/routes/posts/+page.server.ts
|
|
import { error, fail } from '@sveltejs/kit'
|
|
|
|
export const actions = {
|
|
createPost: async ({ request, locals: { supabase, safeGetSession } }) => {
|
|
const { session } = await safeGetSession()
|
|
|
|
if (!session) {
|
|
// the user is not signed in
|
|
throw error(401, { message: 'Unauthorized' })
|
|
}
|
|
// we are save, let the user create the post
|
|
const formData = await request.formData()
|
|
const content = formData.get('content')
|
|
|
|
const { error: createPostError, data: newPost } = await supabase
|
|
.from('posts')
|
|
.insert({ content })
|
|
|
|
if (createPostError) {
|
|
return fail(500, {
|
|
supabaseErrorMessage: createPostError.message,
|
|
})
|
|
}
|
|
return {
|
|
newPost,
|
|
}
|
|
},
|
|
}
|
|
```
|
|
|
|
If you try to submit a form with the action `?/createPost` without a valid session cookie, you will get a 401 error response.
|
|
|
|
### Protecting multiple routes
|
|
|
|
To avoid writing the same auth logic in every single route you can also use the handle hook to
|
|
protect multiple routes at once. For this to work with your Supabase session, you need to use
|
|
SvelteKit's [sequence helper](https://kit.svelte.dev/docs/modules#sveltejs-kit-hooks) function.
|
|
Edit your `/src/hooks.server.js` with the below:
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="js"
|
|
queryGroup="language"
|
|
>
|
|
<TabPanel id="js" label="JavaScript">
|
|
|
|
```js src/hooks.server.js
|
|
// src/hooks.server.js
|
|
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public'
|
|
import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit'
|
|
import { redirect, error } from '@sveltejs/kit'
|
|
import { sequence } from '@sveltejs/kit/hooks'
|
|
|
|
async function supabase({ event, resolve }) {
|
|
event.locals.supabase = createSupabaseServerClient({
|
|
supabaseUrl: PUBLIC_SUPABASE_URL,
|
|
supabaseKey: PUBLIC_SUPABASE_PUBLISHABLE_KEY,
|
|
event,
|
|
})
|
|
|
|
/**
|
|
* Unlike `supabase.auth.getSession`, which is unsafe on the server because it
|
|
* doesn't validate the JWT, this function validates the JWT by first calling
|
|
* `getUser` and aborts early if the JWT signature is invalid.
|
|
*/
|
|
event.locals.safeGetSession = async () => {
|
|
const {
|
|
data: { user },
|
|
error,
|
|
} = await event.locals.supabase.auth.getUser()
|
|
if (error) return { session: null, user: null }
|
|
|
|
const {
|
|
data: { session },
|
|
} = await event.locals.supabase.auth.getSession()
|
|
return { session, user }
|
|
}
|
|
|
|
return resolve(event, {
|
|
filterSerializedResponseHeaders(name) {
|
|
return name === 'content-range' || name === 'x-supabase-api-version'
|
|
},
|
|
})
|
|
}
|
|
|
|
async function authorization({ event, resolve }) {
|
|
// protect requests to all routes that start with /protected-routes
|
|
if (event.url.pathname.startsWith('/protected-routes') && event.request.method === 'GET') {
|
|
const { session } = await event.locals.safeGetSession()
|
|
if (!session) {
|
|
// the user is not signed in
|
|
redirect(303, '/')
|
|
}
|
|
}
|
|
|
|
// protect POST requests to all routes that start with /protected-posts
|
|
if (event.url.pathname.startsWith('/protected-posts') && event.request.method === 'POST') {
|
|
const { session } = await event.locals.safeGetSession()
|
|
if (!session) {
|
|
// the user is not signed in
|
|
throw error(303, '/')
|
|
}
|
|
}
|
|
|
|
return resolve(event)
|
|
}
|
|
|
|
export const handle = sequence(supabase, authorization)
|
|
```
|
|
|
|
</TabPanel>
|
|
|
|
<TabPanel id="ts" label="TypeScript">
|
|
|
|
```ts src/hooks.server.ts
|
|
// src/hooks.server.ts
|
|
import { type Handle, redirect, error } from '@sveltejs/kit'
|
|
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public'
|
|
import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit'
|
|
import { sequence } from '@sveltejs/kit/hooks'
|
|
|
|
async function supabase({ event, resolve }) {
|
|
event.locals.supabase = createSupabaseServerClient({
|
|
supabaseUrl: PUBLIC_SUPABASE_URL,
|
|
supabaseKey: PUBLIC_SUPABASE_PUBLISHABLE_KEY,
|
|
event,
|
|
})
|
|
|
|
/**
|
|
* Unlike `supabase.auth.getSession`, which is unsafe on the server because it
|
|
* doesn't validate the JWT, this function validates the JWT by first calling
|
|
* `getUser` and aborts early if the JWT signature is invalid.
|
|
*/
|
|
event.locals.safeGetSession = async () => {
|
|
const {
|
|
data: { user },
|
|
error,
|
|
} = await event.locals.supabase.auth.getUser()
|
|
if (error) return { session: null, user: null }
|
|
|
|
const {
|
|
data: { session },
|
|
} = await event.locals.supabase.auth.getSession()
|
|
return { session, user }
|
|
}
|
|
|
|
return resolve(event, {
|
|
filterSerializedResponseHeaders(name) {
|
|
return name === 'content-range' || name === 'x-supabase-api-version'
|
|
},
|
|
})
|
|
}
|
|
|
|
async function authorization({ event, resolve }) {
|
|
// protect requests to all routes that start with /protected-routes
|
|
if (event.url.pathname.startsWith('/protected-routes') && event.request.method === 'GET') {
|
|
const { session } = await event.locals.safeGetSession()
|
|
if (!session) {
|
|
// the user is not signed in
|
|
redirect(303, '/')
|
|
}
|
|
}
|
|
|
|
// protect POST requests to all routes that start with /protected-posts
|
|
if (event.url.pathname.startsWith('/protected-posts') && event.request.method === 'POST') {
|
|
const { session } = await event.locals.safeGetSession()
|
|
if (!session) {
|
|
// the user is not signed in
|
|
throw error(303, '/')
|
|
}
|
|
}
|
|
|
|
return resolve(event)
|
|
}
|
|
|
|
export const handle: Handle = sequence(supabase, authorization)
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
## Data fetching
|
|
|
|
### Client-side data fetching with RLS
|
|
|
|
For [row level security](/docs/guides/database/postgres/row-level-security) to work properly when fetching data client-side, you need to use `supabaseClient` from `PageData` and only run your query once the session is defined client-side:
|
|
|
|
```svelte src/routes/+page.svelte
|
|
<script lang="ts">
|
|
export let data
|
|
|
|
let loadedData = []
|
|
async function loadData() {
|
|
const { data: result } = await data.supabase.from('test').select('*').limit(20)
|
|
loadedData = result
|
|
}
|
|
|
|
$: if (data.session) {
|
|
loadData()
|
|
}
|
|
</script>
|
|
|
|
{#if data.session}
|
|
<p>client-side data fetching with RLS</p>
|
|
<pre>{JSON.stringify(loadedData, null, 2)}</pre>
|
|
{/if}
|
|
```
|
|
|
|
### Server-side data fetching with RLS
|
|
|
|
```svelte src/routes/profile/+page.svelte
|
|
<!-- src/routes/profile/+page.svelte -->
|
|
<script lang="ts">
|
|
export let data
|
|
|
|
let { user, tableData } = data
|
|
$: ({ user, tableData } = data)
|
|
</script>
|
|
|
|
<div>Protected content for {user.email}</div>
|
|
<pre>{JSON.stringify(tableData, null, 2)}</pre>
|
|
<pre>{JSON.stringify(user, null, 2)}</pre>
|
|
```
|
|
|
|
```ts src/routes/profile/+page.ts
|
|
// src/routes/profile/+page.ts
|
|
import { redirect } from '@sveltejs/kit'
|
|
|
|
export const load = async ({ parent }) => {
|
|
const { supabase, session } = await parent()
|
|
if (!session) {
|
|
redirect(303, '/')
|
|
}
|
|
const { data: tableData } = await supabase.from('test').select('*')
|
|
|
|
return {
|
|
user: session.user,
|
|
tableData,
|
|
}
|
|
}
|
|
```
|
|
|
|
## Saving and deleting the session
|
|
|
|
```ts
|
|
import { fail, redirect } from '@sveltejs/kit'
|
|
import { AuthApiError } from '@supabase/supabase-js'
|
|
|
|
export const actions = {
|
|
signin: async ({ request, locals: { supabase } }) => {
|
|
const formData = await request.formData()
|
|
|
|
const email = formData.get('email') as string
|
|
const password = formData.get('password') as string
|
|
|
|
const { error } = await supabase.auth.signInWithPassword({
|
|
email,
|
|
password,
|
|
})
|
|
|
|
if (error) {
|
|
if (error instanceof AuthApiError && error.status === 400) {
|
|
return fail(400, {
|
|
error: 'Invalid credentials.',
|
|
values: {
|
|
email,
|
|
},
|
|
})
|
|
}
|
|
return fail(500, {
|
|
error: 'Server error. Try again later.',
|
|
values: {
|
|
email,
|
|
},
|
|
})
|
|
}
|
|
|
|
redirect(303, '/dashboard')
|
|
},
|
|
|
|
signout: async ({ locals: { supabase } }) => {
|
|
await supabase.auth.signOut()
|
|
redirect(303, '/')
|
|
},
|
|
}
|
|
```
|
|
|
|
## Migration guide [#migration]
|
|
|
|
### Migrate to 0.10
|
|
|
|
#### PKCE Auth flow
|
|
|
|
Proof Key for Code Exchange (PKCE) is the new server-side auth flow implemented by the SvelteKit Auth Helpers. It requires a server endpoint for `/auth/callback` that exchanges an auth `code` for the user's `session`.
|
|
|
|
Check the [Code Exchange Route steps](/docs/guides/auth/auth-helpers/sveltekit#code-exchange-route) above to implement this server endpoint.
|
|
|
|
#### Authentication
|
|
|
|
For authentication methods that have a `redirectTo` or `emailRedirectTo`, this must be set to this new code exchange route handler - `/auth/callback`. This is an example with the `signUp` function:
|
|
|
|
```ts
|
|
await supabase.auth.signUp({
|
|
email: 'valid.email@supabase.io',
|
|
password: 'sup3rs3cur3',
|
|
options: {
|
|
emailRedirectTo: 'http://localhost:3000/auth/callback',
|
|
},
|
|
})
|
|
```
|
|
|
|
### Migrate from 0.8.x to 0.9 [#migration-0-9]
|
|
|
|
#### Set up the Supabase client [#migration-set-up-supabase-client]
|
|
|
|
In version 0.9 we now setup our Supabase client for the server inside of a `hooks.server.ts` file.
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="older-0.8"
|
|
queryGroup="migration-version"
|
|
>
|
|
<TabPanel id="older-0.8" label="0.8.x">
|
|
|
|
```js src/lib/db.ts
|
|
// src/lib/db.ts
|
|
import { createClient } from '@supabase/auth-helpers-sveltekit'
|
|
import { env } from '$env/dynamic/public'
|
|
// or use the static env
|
|
|
|
// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public';
|
|
|
|
export const supabaseClient = createClient(
|
|
env.PUBLIC_SUPABASE_URL,
|
|
env.PUBLIC_SUPABASE_PUBLISHABLE_KEY
|
|
)
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="0.9.0" label="0.9.0">
|
|
|
|
```js src/hooks.server.ts
|
|
// src/hooks.server.ts
|
|
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public'
|
|
import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit'
|
|
import type { Handle } from '@sveltejs/kit'
|
|
|
|
export const handle: Handle = async ({ event, resolve }) => {
|
|
event.locals.supabase = createSupabaseServerClient({
|
|
supabaseUrl: PUBLIC_SUPABASE_URL,
|
|
supabaseKey: PUBLIC_SUPABASE_PUBLISHABLE_KEY,
|
|
event,
|
|
})
|
|
|
|
/**
|
|
* Unlike `supabase.auth.getSession`, which is unsafe on the server because it
|
|
* doesn't validate the JWT, this function validates the JWT by first calling
|
|
* `getUser` and aborts early if the JWT signature is invalid.
|
|
*/
|
|
event.locals.safeGetSession = async () => {
|
|
const {
|
|
data: { user },
|
|
error,
|
|
} = await event.locals.supabase.auth.getUser()
|
|
if (error) return { session: null, user: null }
|
|
|
|
const {
|
|
data: { session },
|
|
} = await event.locals.supabase.auth.getSession()
|
|
return { session, user }
|
|
}
|
|
|
|
return resolve(event, {
|
|
filterSerializedResponseHeaders(name) {
|
|
return name === 'content-range' || name === 'x-supabase-api-version'
|
|
},
|
|
})
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
#### Initialize the client [#migration-initialize-client]
|
|
|
|
In order to use the Supabase library in your client code you will need to setup a shared load function inside the root `+layout.ts` and create a `+layout.svelte` to handle our event listening for Auth events.
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="older-0.8"
|
|
queryGroup="migration-version"
|
|
>
|
|
<TabPanel id="older-0.8" label="0.8.x">
|
|
|
|
```svelte src/routes/+layout.svelte
|
|
<!-- src/routes/+layout.svelte -->
|
|
<script lang="ts">
|
|
import { supabaseClient } from '$lib/db'
|
|
import { invalidate } from '$app/navigation'
|
|
import { onMount } from 'svelte'
|
|
|
|
onMount(() => {
|
|
const {
|
|
data: { subscription },
|
|
} = supabaseClient.auth.onAuthStateChange(() => {
|
|
invalidate('supabase:auth')
|
|
})
|
|
|
|
return () => {
|
|
subscription.unsubscribe()
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<slot />
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="0.9.0" label="0.9.0">
|
|
|
|
```ts src/routes/+layout.ts
|
|
// src/routes/+layout.ts
|
|
import { invalidate } from '$app/navigation'
|
|
import { PUBLIC_SUPABASE_PUBLISHABLE_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'
|
|
import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit'
|
|
import type { LayoutLoad } from './$types'
|
|
import type { Database } from '../DatabaseDefinitions'
|
|
|
|
export const load: LayoutLoad = async ({ fetch, data, depends }) => {
|
|
depends('supabase:auth')
|
|
|
|
const supabase = createSupabaseLoadClient<Database>({
|
|
supabaseUrl: PUBLIC_SUPABASE_URL,
|
|
supabaseKey: PUBLIC_SUPABASE_PUBLISHABLE_KEY,
|
|
event: { fetch },
|
|
serverSession: data.session,
|
|
})
|
|
|
|
const {
|
|
data: { session },
|
|
} = await supabase.auth.getSession()
|
|
|
|
return { supabase, session }
|
|
}
|
|
```
|
|
|
|
```svelte src/routes/+layout.svelte
|
|
<!-- src/routes/+layout.svelte -->
|
|
<script lang="ts">
|
|
import { invalidate } from '$app/navigation';
|
|
import { onMount } from 'svelte';
|
|
import type { LayoutData } from './$types';
|
|
|
|
export let data: LayoutData;
|
|
|
|
$: ({ supabase, session } = data);
|
|
|
|
onMount(() => {
|
|
const {
|
|
data: { subscription },
|
|
} = supabase.auth.onAuthStateChange((event, _session) => {
|
|
if (_session?.expires_at !== session?.expires_at) {
|
|
invalidate('supabase:auth')
|
|
}
|
|
});
|
|
|
|
return () => subscription.unsubscribe();
|
|
});
|
|
</script>
|
|
|
|
<slot />
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
#### Set up hooks [#migration-set-up-hooks]
|
|
|
|
Since version 0.9 relies on `hooks.server.ts` to setup our client, we no longer need the `hooks.client.ts` in our project for Supabase related code.
|
|
|
|
#### Types [#migration-typings]
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="older-0-8"
|
|
queryGroup="migration-version"
|
|
>
|
|
<TabPanel id="older-0-8" label="0.8.x">
|
|
|
|
```ts src/app.d.ts
|
|
// src/app.d.ts
|
|
/// <reference types="@sveltejs/kit" />
|
|
|
|
// See https://kit.svelte.dev/docs/types#app
|
|
// for information about these interfaces
|
|
// and what to do when importing types
|
|
declare namespace App {
|
|
interface Supabase {
|
|
Database: import('./DatabaseDefinitions').Database
|
|
SchemaName: 'public'
|
|
}
|
|
|
|
// interface Locals {}
|
|
interface PageData {
|
|
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession
|
|
}
|
|
// interface Error {}
|
|
// interface Platform {}
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="0.9.0" label="0.9.0">
|
|
|
|
```ts src/app.d.ts
|
|
// src/app.d.ts
|
|
import { SupabaseClient, Session, User } from '@supabase/supabase-js'
|
|
import { Database } from './DatabaseDefinitions'
|
|
|
|
declare global {
|
|
namespace App {
|
|
interface Locals {
|
|
supabase: SupabaseClient<Database>
|
|
safeGetSession(): Promise<{ session: Session | null; user: User | null }>
|
|
}
|
|
interface PageData {
|
|
session: Session | null
|
|
user: User | null
|
|
}
|
|
// interface Error {}
|
|
// interface Platform {}
|
|
}
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
#### Protecting a page [#migration-protecting-a-page]
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="older-0-8"
|
|
queryGroup="migration-version"
|
|
>
|
|
<TabPanel id="older-0-8" label="0.8.x">
|
|
|
|
```svelte src/routes/profile/+page.svelte
|
|
<!-- src/routes/profile/+page.svelte -->
|
|
<script lang="ts">
|
|
/** @type {import('./$types').PageData} */
|
|
export let data
|
|
$: ({ user, tableData } = data)
|
|
</script>
|
|
|
|
<div>Protected content for {user.email}</div>
|
|
<pre>{JSON.stringify(tableData, null, 2)}</pre>
|
|
<pre>{JSON.stringify(user, null, 2)}</pre>
|
|
```
|
|
|
|
```ts src/routes/profile/+page.ts
|
|
// src/routes/profile/+page.ts
|
|
import type { PageLoad } from './$types'
|
|
import { getSupabase } from '@supabase/auth-helpers-sveltekit'
|
|
import { redirect } from '@sveltejs/kit'
|
|
|
|
export const load: PageLoad = async (event) => {
|
|
const { session, supabaseClient } = await getSupabase(event)
|
|
if (!session) {
|
|
redirect(303, '/')
|
|
}
|
|
const { data: tableData } = await supabaseClient.from('test').select('*')
|
|
|
|
return {
|
|
user: session.user,
|
|
tableData,
|
|
}
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="0.9.0" label="0.9.0">
|
|
|
|
```svelte src/routes/profile/+page.svelte
|
|
<!-- src/routes/profile/+page.svelte -->
|
|
<script lang="ts">
|
|
import type { PageData } from './$types'
|
|
|
|
export let data: PageData
|
|
$: ({ user, tableData } = data)
|
|
</script>
|
|
|
|
<div>Protected content for {user.email}</div>
|
|
<pre>{JSON.stringify(tableData, null, 2)}</pre>
|
|
<pre>{JSON.stringify(user, null, 2)}</pre>
|
|
```
|
|
|
|
```ts src/routes/profile/+page.ts
|
|
// src/routes/profile/+page.ts
|
|
import type { PageLoad } from './$types'
|
|
import { redirect } from '@sveltejs/kit'
|
|
|
|
export const load: PageLoad = async ({ parent }) => {
|
|
const { supabase, session } = await parent()
|
|
if (!session) {
|
|
redirect(303, '/')
|
|
}
|
|
const { data: tableData } = await supabase.from('test').select('*')
|
|
|
|
return {
|
|
user: session.user,
|
|
tableData,
|
|
}
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
#### Protecting a API route [#migration-protecting-a-api-route]
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="older-0-8"
|
|
queryGroup="migration-version"
|
|
>
|
|
<TabPanel id="older-0-8" label="0.8.x">
|
|
|
|
```ts src/routes/api/protected-route/+server.ts
|
|
// src/routes/api/protected-route/+server.ts
|
|
import type { RequestHandler } from './$types'
|
|
import { getSupabase } from '@supabase/auth-helpers-sveltekit'
|
|
import { json, redirect } from '@sveltejs/kit'
|
|
|
|
export const GET: RequestHandler = async (event) => {
|
|
const { session, supabaseClient } = await getSupabase(event)
|
|
if (!session) {
|
|
redirect(303, '/')
|
|
}
|
|
const { data } = await supabaseClient.from('test').select('*')
|
|
|
|
return json({ data })
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="0.9.0" label="0.9.0">
|
|
|
|
```ts src/routes/api/protected-route/+server.ts
|
|
// src/routes/api/protected-route/+server.ts
|
|
import type { RequestHandler } from './$types'
|
|
import { json, error } from '@sveltejs/kit'
|
|
|
|
export const GET: RequestHandler = async ({ locals: { supabase, getSession } }) => {
|
|
const { session } = await getSession()
|
|
if (!session) {
|
|
// the user is not signed in
|
|
throw error(401, { message: 'Unauthorized' })
|
|
}
|
|
const { data } = await supabase.from('test').select('*')
|
|
|
|
return json({ data })
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
### Migrate from 0.7.x to 0.8 [#migration-0-8]
|
|
|
|
#### Set up the Supabase client [#migration-set-up-supabase-client-0-8]
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="older-0.7"
|
|
queryGroup="migration-version"
|
|
>
|
|
<TabPanel id="older-0.7" label="0.7.x">
|
|
|
|
```js src/lib/db.ts
|
|
import { createClient } from '@supabase/supabase-js'
|
|
import { setupSupabaseHelpers } from '@supabase/auth-helpers-sveltekit'
|
|
import { dev } from '$app/environment'
|
|
import { env } from '$env/dynamic/public'
|
|
// or use the static env
|
|
|
|
// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public';
|
|
|
|
export const supabaseClient = createClient(
|
|
env.PUBLIC_SUPABASE_URL,
|
|
env.PUBLIC_SUPABASE_PUBLISHABLE_KEY,
|
|
{
|
|
persistSession: false,
|
|
autoRefreshToken: false,
|
|
}
|
|
)
|
|
|
|
setupSupabaseHelpers({
|
|
supabaseClient,
|
|
cookieOptions: {
|
|
secure: !dev,
|
|
},
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="0.8.0" label="0.8.0">
|
|
|
|
```js src/lib/db.ts
|
|
import { createClient } from '@supabase/auth-helpers-sveltekit'
|
|
import { env } from '$env/dynamic/public'
|
|
// or use the static env
|
|
|
|
// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public';
|
|
|
|
export const supabaseClient = createClient(
|
|
env.PUBLIC_SUPABASE_URL,
|
|
env.PUBLIC_SUPABASE_PUBLISHABLE_KEY
|
|
)
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
#### Initialize the client [#migration-initialize-client-0-8]
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="older-0.7"
|
|
queryGroup="migration-version"
|
|
>
|
|
<TabPanel id="older-0.7" label="0.7.x">
|
|
|
|
```svelte src/routes/+layout.svelte
|
|
<script lang="ts">
|
|
// make sure the supabase instance is initialized on the client
|
|
import '$lib/db'
|
|
import { startSupabaseSessionSync } from '@supabase/auth-helpers-sveltekit'
|
|
import { page } from '$app/stores'
|
|
import { invalidateAll } from '$app/navigation'
|
|
|
|
// this sets up automatic token refreshing
|
|
startSupabaseSessionSync({
|
|
page,
|
|
handleRefresh: () => invalidateAll(),
|
|
})
|
|
</script>
|
|
|
|
<slot />
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="0.8.0" label="0.8.0">
|
|
|
|
```svelte src/routes/+layout.svelte
|
|
<script>
|
|
import { supabaseClient } from '$lib/db'
|
|
import { invalidate } from '$app/navigation'
|
|
import { onMount } from 'svelte'
|
|
|
|
onMount(() => {
|
|
const {
|
|
data: { subscription },
|
|
} = supabaseClient.auth.onAuthStateChange(() => {
|
|
invalidate('supabase:auth')
|
|
})
|
|
|
|
return () => {
|
|
subscription.unsubscribe()
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<slot />
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
#### Set up hooks [#migration-set-up-hooks-0-8]
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="older-0-7"
|
|
queryGroup="migration-version"
|
|
>
|
|
<TabPanel id="older-0-7" label="0.7.x">
|
|
|
|
```ts src/hooks.server.ts
|
|
// make sure the supabase instance is initialized on the server
|
|
import '$lib/db'
|
|
import { dev } from '$app/environment'
|
|
import { auth } from '@supabase/auth-helpers-sveltekit/server'
|
|
|
|
export const handle = auth()
|
|
```
|
|
|
|
**Optional** _if using additional handle methods_
|
|
|
|
```ts src/hooks.server.ts
|
|
// make sure the supabase instance is initialized on the server
|
|
import '$lib/db'
|
|
import { dev } from '$app/environment'
|
|
import { auth } from '@supabase/auth-helpers-sveltekit/server'
|
|
import { sequence } from '@sveltejs/kit/hooks'
|
|
|
|
export const handle = sequence(auth(), yourHandler)
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="0.8.0" label="0.8.0">
|
|
|
|
```ts src/hooks.server.ts
|
|
// make sure the supabase instance is initialized on the server
|
|
import '$lib/db'
|
|
```
|
|
|
|
```ts src/hooks.client.ts
|
|
// make sure the supabase instance is initialized on the client
|
|
import '$lib/db'
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
#### Types [#migration-typings-0-8]
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="older-0-7"
|
|
queryGroup="migration-version"
|
|
>
|
|
<TabPanel id="older-0-7" label="0.7.x">
|
|
|
|
```ts src/app.d.ts
|
|
/// <reference types="@sveltejs/kit" />
|
|
|
|
// See https://kit.svelte.dev/docs/types#app
|
|
// for information about these interfaces
|
|
// and what to do when importing types
|
|
declare namespace App {
|
|
interface Locals {
|
|
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession
|
|
}
|
|
|
|
interface PageData {
|
|
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession
|
|
}
|
|
|
|
// interface Error {}
|
|
// interface Platform {}
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="0.8.0" label="0.8.0">
|
|
|
|
```ts src/app.d.ts
|
|
/// <reference types="@sveltejs/kit" />
|
|
|
|
// See https://kit.svelte.dev/docs/types#app
|
|
// for information about these interfaces
|
|
// and what to do when importing types
|
|
declare namespace App {
|
|
interface Supabase {
|
|
Database: import('./DatabaseDefinitions').Database
|
|
SchemaName: 'public'
|
|
}
|
|
|
|
// interface Locals {}
|
|
interface PageData {
|
|
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession
|
|
}
|
|
// interface Error {}
|
|
// interface Platform {}
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
#### `withPageAuth` [#migration-with-page-auth-0-8]
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="older-0-7"
|
|
queryGroup="migration-version"
|
|
>
|
|
<TabPanel id="older-0-7" label="0.7.x">
|
|
|
|
```svelte src/routes/protected-route/+page.svelte
|
|
<script lang="ts">
|
|
import type { PageData } from './$types'
|
|
|
|
export let data: PageData
|
|
$: ({ tableData, user } = data)
|
|
</script>
|
|
|
|
<div>Protected content for {user.email}</div>
|
|
<p>server-side fetched data with RLS:</p>
|
|
<pre>{JSON.stringify(tableData, null, 2)}</pre>
|
|
<p>user:</p>
|
|
<pre>{JSON.stringify(user, null, 2)}</pre>
|
|
```
|
|
|
|
```ts src/routes/protected-route/+page.ts
|
|
import { withAuth } from '@supabase/auth-helpers-sveltekit'
|
|
import { redirect } from '@sveltejs/kit'
|
|
import type { PageLoad } from './$types'
|
|
|
|
export const load: PageLoad = withAuth(async ({ session, getSupabaseClient }) => {
|
|
if (!session.user) {
|
|
redirect(303, '/')
|
|
}
|
|
|
|
const { data: tableData } = await getSupabaseClient().from('test').select('*')
|
|
return { tableData, user: session.user }
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="0.8.0" label="0.8.0">
|
|
|
|
```svelte src/routes/protected-route/+page.svelte
|
|
<script>
|
|
/** @type {import('./$types').PageData} */
|
|
export let data
|
|
$: ({ user, tableData } = data)
|
|
</script>
|
|
|
|
<div>Protected content for {user.email}</div>
|
|
<pre>{JSON.stringify(tableData, null, 2)}</pre>
|
|
<pre>{JSON.stringify(user, null, 2)}</pre>
|
|
```
|
|
|
|
```ts src/routes/protected-route/+page.ts
|
|
// src/routes/profile/+page.ts
|
|
import type { PageLoad } from './$types'
|
|
import { getSupabase } from '@supabase/auth-helpers-sveltekit'
|
|
import { redirect } from '@sveltejs/kit'
|
|
|
|
export const load: PageLoad = async (event) => {
|
|
const { session, supabaseClient } = await getSupabase(event)
|
|
if (!session) {
|
|
redirect(303, '/')
|
|
}
|
|
const { data: tableData } = await supabaseClient.from('test').select('*')
|
|
|
|
return {
|
|
user: session.user,
|
|
tableData,
|
|
}
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
#### `withApiAuth` [#migration-with-api-auth-0-8]
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="older-0-7"
|
|
queryGroup="migration-version"
|
|
>
|
|
<TabPanel id="older-0-7" label="0.7.x">
|
|
|
|
```ts src/routes/api/protected-route/+server.ts
|
|
import type { RequestHandler } from './$types'
|
|
import { withAuth } from '@supabase/auth-helpers-sveltekit'
|
|
import { json, redirect } from '@sveltejs/kit'
|
|
|
|
interface TestTable {
|
|
id: string
|
|
created_at: string
|
|
}
|
|
|
|
export const GET: RequestHandler = withAuth(async ({ session, getSupabaseClient }) => {
|
|
if (!session.user) {
|
|
redirect(303, '/')
|
|
}
|
|
|
|
const { data } = await getSupabaseClient().from<TestTable>('test').select('*')
|
|
|
|
return json({ data })
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="0.8.0" label="0.8.0">
|
|
|
|
```ts src/routes/api/protected-route/+server.ts
|
|
import type { RequestHandler } from './$types'
|
|
import { getSupabase } from '@supabase/auth-helpers-sveltekit'
|
|
import { json, redirect } from '@sveltejs/kit'
|
|
|
|
export const GET: RequestHandler = async (event) => {
|
|
const { session, supabaseClient } = await getSupabase(event)
|
|
if (!session) {
|
|
redirect(303, '/')
|
|
}
|
|
const { data } = await supabaseClient.from('test').select('*')
|
|
|
|
return json({ data })
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
### Migrate from 0.6.11 and below to 0.7.0 [#migration-0-7]
|
|
|
|
There are numerous breaking changes in the latest 0.7.0 version of this library.
|
|
|
|
#### Environment variable prefix
|
|
|
|
The environment variable prefix is now `PUBLIC_` instead of `VITE_` (e.g., `VITE_SUPABASE_URL` is now `PUBLIC_SUPABASE_URL`).
|
|
|
|
#### Set up the Supabase client [#migration-set-up-supabase-client-0-7]
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="older"
|
|
queryGroup="migration-version"
|
|
>
|
|
<TabPanel id="older" label="0.6.11 and below">
|
|
|
|
```js src/lib/db.ts
|
|
import { createSupabaseClient } from '@supabase/auth-helpers-sveltekit';
|
|
|
|
const { supabaseClient } = createSupabaseClient(
|
|
import.meta.env.VITE_SUPABASE_URL as string,
|
|
import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY as string
|
|
);
|
|
|
|
export { supabaseClient };
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="0.7.0" label="0.7.0">
|
|
|
|
```js src/lib/db.ts
|
|
import { createClient } from '@supabase/supabase-js'
|
|
import { setupSupabaseHelpers } from '@supabase/auth-helpers-sveltekit'
|
|
import { dev } from '$app/environment'
|
|
import { env } from '$env/dynamic/public'
|
|
// or use the static env
|
|
|
|
// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_PUBLISHABLE_KEY } from '$env/static/public';
|
|
|
|
export const supabaseClient = createClient(
|
|
env.PUBLIC_SUPABASE_URL,
|
|
env.PUBLIC_SUPABASE_PUBLISHABLE_KEY,
|
|
{
|
|
persistSession: false,
|
|
autoRefreshToken: false,
|
|
}
|
|
)
|
|
|
|
setupSupabaseHelpers({
|
|
supabaseClient,
|
|
cookieOptions: {
|
|
secure: !dev,
|
|
},
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
#### Initialize the client [#migration-initialize-client-0-7]
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="older"
|
|
queryGroup="migration-version"
|
|
>
|
|
<TabPanel id="older" label="0.6.11 and below">
|
|
|
|
```svelte src/routes/__layout.svelte
|
|
<script>
|
|
import { session } from '$app/stores'
|
|
import { supabaseClient } from '$lib/db'
|
|
import { SupaAuthHelper } from '@supabase/auth-helpers-svelte'
|
|
</script>
|
|
|
|
<SupaAuthHelper {supabaseClient} {session}>
|
|
<slot />
|
|
</SupaAuthHelper>
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="0.7.0" label="0.7.0">
|
|
|
|
The `@supabase/auth-helpers-svelte` library is no longer required as the `@supabase/auth-helpers-sveltekit` library handles all the client-side code.
|
|
|
|
```svelte src/routes/+layout.svelte
|
|
<script lang="ts">
|
|
// make sure the supabase instance is initialized on the client
|
|
import '$lib/db'
|
|
import { startSupabaseSessionSync } from '@supabase/auth-helpers-sveltekit'
|
|
import { page } from '$app/stores'
|
|
import { invalidateAll } from '$app/navigation'
|
|
|
|
// this sets up automatic token refreshing
|
|
startSupabaseSessionSync({
|
|
page,
|
|
handleRefresh: () => invalidateAll(),
|
|
})
|
|
</script>
|
|
|
|
<slot />
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
#### Set up hooks [#migration-set-up-hooks-0-7]
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="older"
|
|
queryGroup="migration-version"
|
|
>
|
|
<TabPanel id="older" label="0.6.11 and below">
|
|
|
|
```ts src/hooks.ts
|
|
import { handleAuth } from '@supabase/auth-helpers-sveltekit'
|
|
import type { GetSession, Handle } from '@sveltejs/kit'
|
|
import { sequence } from '@sveltejs/kit/hooks'
|
|
|
|
export const handle: Handle = sequence(...handleAuth())
|
|
|
|
export const getSession: GetSession = async (event) => {
|
|
const { user, accessToken, error } = event.locals
|
|
return {
|
|
user,
|
|
accessToken,
|
|
error,
|
|
}
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="0.7.0" label="0.7.0">
|
|
|
|
```ts src/hooks.server.ts
|
|
// make sure the supabase instance is initialized on the server
|
|
import '$lib/db'
|
|
import { dev } from '$app/environment'
|
|
import { auth } from '@supabase/auth-helpers-sveltekit/server'
|
|
|
|
export const handle = auth()
|
|
```
|
|
|
|
**Optional** _if using additional handle methods_
|
|
|
|
```ts src/hooks.server.ts
|
|
// make sure the supabase instance is initialized on the server
|
|
import '$lib/db'
|
|
import { dev } from '$app/environment'
|
|
import { auth } from '@supabase/auth-helpers-sveltekit/server'
|
|
import { sequence } from '@sveltejs/kit/hooks'
|
|
|
|
export const handle = sequence(auth(), yourHandler)
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
#### Types [#migration-typings-0-7]
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="older"
|
|
queryGroup="migration-version"
|
|
>
|
|
<TabPanel id="older" label="0.6.11 and below">
|
|
|
|
```ts src/app.d.ts
|
|
/// <reference types="@sveltejs/kit" />
|
|
// See https://kit.svelte.dev/docs/types#app
|
|
// for information about these interfaces
|
|
declare namespace App {
|
|
interface UserSession {
|
|
user: import('@supabase/supabase-js').User
|
|
accessToken?: string
|
|
}
|
|
|
|
interface Locals extends UserSession {
|
|
error: import('@supabase/supabase-js').ApiError
|
|
}
|
|
|
|
interface Session extends UserSession {}
|
|
|
|
// interface Platform {}
|
|
// interface Stuff {}
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="0.7.0" label="0.7.0">
|
|
|
|
```ts src/app.d.ts
|
|
/// <reference types="@sveltejs/kit" />
|
|
|
|
// See https://kit.svelte.dev/docs/types#app
|
|
// for information about these interfaces
|
|
// and what to do when importing types
|
|
declare namespace App {
|
|
interface Locals {
|
|
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession
|
|
}
|
|
|
|
interface PageData {
|
|
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession
|
|
}
|
|
|
|
// interface Error {}
|
|
// interface Platform {}
|
|
}
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
#### Check the user on the client
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="older"
|
|
queryGroup="migration-version"
|
|
>
|
|
<TabPanel id="older" label="0.6.11 and below">
|
|
|
|
```svelte src/routes/index.svelte
|
|
<script>
|
|
import { session } from '$app/stores'
|
|
</script>
|
|
|
|
{#if !$session.user}
|
|
<h1>I am not logged in</h1>
|
|
{:else}
|
|
<h1>Welcome {$session.user.email}</h1>
|
|
<p>I am logged in!</p>
|
|
{/if}
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="0.7.0" label="0.7.0">
|
|
|
|
```svelte src/routes/+page.svelte
|
|
<script>
|
|
import { page } from '$app/stores'
|
|
</script>
|
|
|
|
{#if !$page.data.session.user}
|
|
<h1>I am not logged in</h1>
|
|
{:else}
|
|
<h1>Welcome {$page.data.session.user.email}</h1>
|
|
<p>I am logged in!</p>
|
|
{/if}
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
#### `withPageAuth`
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="older"
|
|
queryGroup="migration-version"
|
|
>
|
|
<TabPanel id="older" label="0.6.11 and below">
|
|
|
|
```svelte src/routes/protected-route.svelte
|
|
<script lang="ts" context="module">
|
|
import { supabaseServerClient, withPageAuth } from '@supabase/auth-helpers-sveltekit'
|
|
import type { Load } from './__types/protected-page'
|
|
|
|
export const load: Load = async ({ session }) =>
|
|
withPageAuth(
|
|
{
|
|
redirectTo: '/',
|
|
user: session.user,
|
|
},
|
|
async () => {
|
|
const { data } = await supabaseServerClient(session.accessToken).from('test').select('*')
|
|
return { props: { data, user: session.user } }
|
|
}
|
|
)
|
|
</script>
|
|
|
|
<script>
|
|
export let data
|
|
export let user
|
|
</script>
|
|
|
|
<div>Protected content for {user.email}</div>
|
|
<p>server-side fetched data with RLS:</p>
|
|
<pre>{JSON.stringify(data, null, 2)}</pre>
|
|
<p>user:</p>
|
|
<pre>{JSON.stringify(user, null, 2)}</pre>
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="0.7.0" label="0.7.0">
|
|
|
|
```svelte src/routes/protected-route/+page.svelte
|
|
<script lang="ts">
|
|
import type { PageData } from './$types'
|
|
|
|
export let data: PageData
|
|
$: ({ tableData, user } = data)
|
|
</script>
|
|
|
|
<div>Protected content for {user.email}</div>
|
|
<p>server-side fetched data with RLS:</p>
|
|
<pre>{JSON.stringify(tableData, null, 2)}</pre>
|
|
<p>user:</p>
|
|
<pre>{JSON.stringify(user, null, 2)}</pre>
|
|
```
|
|
|
|
```ts src/routes/protected-route/+page.ts
|
|
import { withAuth } from '@supabase/auth-helpers-sveltekit'
|
|
import { redirect } from '@sveltejs/kit'
|
|
import type { PageLoad } from './$types'
|
|
|
|
export const load: PageLoad = withAuth(async ({ session, getSupabaseClient }) => {
|
|
if (!session.user) {
|
|
redirect(303, '/')
|
|
}
|
|
|
|
const { data: tableData } = await getSupabaseClient().from('test').select('*')
|
|
return { tableData, user: session.user }
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
#### `withApiAuth`
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="older"
|
|
queryGroup="migration-version"
|
|
>
|
|
<TabPanel id="older" label="0.6.11 and below">
|
|
|
|
```ts src/routes/api/protected-route.ts
|
|
import { supabaseServerClient, withApiAuth } from '@supabase/auth-helpers-sveltekit'
|
|
import type { RequestHandler } from './__types/protected-route'
|
|
|
|
interface TestTable {
|
|
id: string
|
|
created_at: string
|
|
}
|
|
|
|
interface GetOutput {
|
|
data: TestTable[]
|
|
}
|
|
|
|
export const GET: RequestHandler<GetOutput> = async ({ locals, request }) =>
|
|
withApiAuth({ user: locals.user }, async () => {
|
|
// Run queries with RLS on the server
|
|
const { data } = await supabaseServerClient(request).from('test').select('*')
|
|
|
|
return {
|
|
status: 200,
|
|
body: { data },
|
|
}
|
|
})
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="0.7.0" label="0.7.0">
|
|
|
|
```ts src/routes/api/protected-route/+server.ts
|
|
import type { RequestHandler } from './$types';
|
|
import { withAuth } from '@supabase/auth-helpers-sveltekit';
|
|
import { json, redirect } from '@sveltejs/kit';
|
|
|
|
interface TestTable {
|
|
id: string;
|
|
created_at: string;
|
|
}
|
|
|
|
export const GET: RequestHandler = withAuth(async ({ session, getSupabaseClient }) => {
|
|
if (!session.user) {
|
|
redirect(303, '/');
|
|
}
|
|
|
|
const { data } = await getSupabaseClient()
|
|
.from<TestTable>('test')
|
|
.select('*');
|
|
|
|
return json({ data });
|
|
);
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
## Additional links
|
|
|
|
- [Auth Helpers Source code](https://github.com/supabase/auth-helpers)
|
|
- [SvelteKit example](https://github.com/supabase/auth-helpers/tree/main/examples/sveltekit)
|
|
|
|
</AccordionItem>
|
|
|
|
</Accordion>
|