mirror of
https://github.com/supabase/supabase.git
synced 2026-10-07 10:25:06 +03:00
Update guide to include PKCE auth flow
This commit is contained in:
1 parent
1d5a53e8fd
commit
8dfd621c60
1 file changed
+572
-187
@@ -9,7 +9,9 @@ export const meta = {
|
||||
|
||||
This submodule provides convenience helpers for implementing user authentication in [SvelteKit](https://kit.svelte.dev/) applications.
|
||||
|
||||
## Installation
|
||||
## Configuration
|
||||
|
||||
### Install SvelteKit Auth Helpers library
|
||||
|
||||
This library supports Node.js `^16.15.0`.
|
||||
|
||||
@@ -17,21 +19,65 @@ This library supports Node.js `^16.15.0`.
|
||||
npm install @supabase/auth-helpers-sveltekit
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
### Declare Environment Variables
|
||||
|
||||
### Configuration
|
||||
Retrieve your project's URL and anon key from your [API settings](https://app.supabase.com/project/_/settings/api), and create a `.env.local` file with the following environment variables:
|
||||
|
||||
Set up the following env vars. For local development you can set them in a `.env` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/sveltekit/.env.example).
|
||||
|
||||
```bash
|
||||
```bash title=".env.local"
|
||||
# Find these in your Supabase project settings https://app.supabase.com/project/_/settings/api
|
||||
PUBLIC_SUPABASE_URL=https://your-project.supabase.co
|
||||
PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
||||
```
|
||||
|
||||
### Set up the Supabase client
|
||||
### Creating a Supabase Client
|
||||
|
||||
Create a server supabase client in a handle hook:
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
Create a new `hooks.server.js` file in the root of your project and populate with the following:
|
||||
|
||||
```js title=src/hooks.server.js
|
||||
// src/hooks.server.js
|
||||
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_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_ANON_KEY,
|
||||
event,
|
||||
})
|
||||
|
||||
/**
|
||||
* a little helper that is written for convenience so that instead
|
||||
* of calling `const { data: { session } } = await supabase.auth.getSession()`
|
||||
* you just call this `await getSession()`
|
||||
*/
|
||||
event.locals.getSession = async () => {
|
||||
const {
|
||||
data: { session },
|
||||
} = await event.locals.supabase.auth.getSession()
|
||||
return session
|
||||
}
|
||||
|
||||
return resolve(event, {
|
||||
filterSerializedResponseHeaders(name) {
|
||||
return name === 'content-range'
|
||||
},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</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 title=src/hooks.server.ts
|
||||
// src/hooks.server.ts
|
||||
@@ -59,11 +105,6 @@ export const handle: Handle = async ({ event, resolve }) => {
|
||||
}
|
||||
|
||||
return resolve(event, {
|
||||
/**
|
||||
* There´s an issue with `filterSerializedResponseHeaders` not working when using `sequence`
|
||||
*
|
||||
* https://github.com/sveltejs/kit/issues/8061
|
||||
*/
|
||||
filterSerializedResponseHeaders(name) {
|
||||
return name === 'content-range'
|
||||
},
|
||||
@@ -71,90 +112,65 @@ export const handle: Handle = async ({ event, resolve }) => {
|
||||
}
|
||||
```
|
||||
|
||||
> Note that we are specifying filterSerializedResponseHeaders here. We need to tell SvelteKit that supabase needs the content-range header.
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Send session to client
|
||||
<Admonition type="info">
|
||||
|
||||
In order to make the session available to the UI (pages, layouts) we need to pass the session in the root layout server load function:
|
||||
Note that we are specifying filterSerializedResponseHeaders here. We need to tell SvelteKit that supabase needs the content-range header.
|
||||
|
||||
```ts title=src/routes/+layout.server.ts
|
||||
// src/routes/+layout.server.ts
|
||||
import type { LayoutServerLoad } from './$types'
|
||||
</Admonition>
|
||||
|
||||
export const load: LayoutServerLoad = async ({ locals: { getSession } }) => {
|
||||
return {
|
||||
session: await getSession(),
|
||||
### Code Exchange Route
|
||||
|
||||
The `Code Exchange` route is required for the [server-side auth flow](https://supabase.com/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"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
Create a new file at `src/routes/auth/callback/+server.js` and populate with the following:
|
||||
|
||||
```jsx title="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)
|
||||
}
|
||||
|
||||
throw redirect(303, '/')
|
||||
}
|
||||
```
|
||||
|
||||
### Shared Load functions and pages
|
||||
</TabPanel>
|
||||
|
||||
To be able to use Supabase in shared load functions and inside pages you need to create a Supabase client in the root layout load:
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
```ts
|
||||
// src/routes/+layout.ts
|
||||
import { PUBLIC_SUPABASE_ANON_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'
|
||||
Create a new file at `src/routes/auth/callback/+server.ts` and populate with the following:
|
||||
|
||||
export const load: LayoutLoad = async ({ fetch, data, depends }) => {
|
||||
depends('supabase:auth')
|
||||
```tsx title="src/routes/auth/callback/+server.ts"
|
||||
import { redirect } from '@sveltejs/kit'
|
||||
|
||||
const supabase = createSupabaseLoadClient<Database>({
|
||||
supabaseUrl: PUBLIC_SUPABASE_URL,
|
||||
supabaseKey: PUBLIC_SUPABASE_ANON_KEY,
|
||||
event: { fetch },
|
||||
serverSession: data.session,
|
||||
})
|
||||
export const GET = async ({ url, locals: { supabase } }) => {
|
||||
const code = url.searchParams.get('code')
|
||||
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
if (code) {
|
||||
await supabase.auth.exchangeCodeForSession(code)
|
||||
}
|
||||
|
||||
return { supabase, session }
|
||||
throw redirect(303, '/')
|
||||
}
|
||||
```
|
||||
|
||||
Access the client inside pages by `$page.data.supabase` or `data.supabase` when using `export let data: PageData`.
|
||||
|
||||
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 catch supabase events being triggered.
|
||||
|
||||
```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 />
|
||||
```
|
||||
|
||||
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.
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Generate types from your database
|
||||
|
||||
@@ -181,78 +197,323 @@ declare global {
|
||||
}
|
||||
```
|
||||
|
||||
## Client-side data fetching with RLS
|
||||
## Authentication
|
||||
|
||||
For [row level security](https://supabase.com/docs/guides/auth/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:
|
||||
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](http://localhost:3001/docs/reference/javascript/auth-api) are supported with the Auth Helpers client.
|
||||
|
||||
```html
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types'
|
||||
<Admonition type="info">
|
||||
|
||||
export let data: PageData
|
||||
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`.
|
||||
|
||||
let loadedData = []
|
||||
async function loadData() {
|
||||
const { data: result } = await data.supabase.from('test').select('*').limit(20)
|
||||
loadedData = result
|
||||
}
|
||||
</Admonition>
|
||||
|
||||
$: if (data.session) {
|
||||
loadData()
|
||||
}
|
||||
</script>
|
||||
### Client-side
|
||||
|
||||
{#if data.session}
|
||||
<p>client-side data fetching with RLS</p>
|
||||
<pre>{JSON.stringify(loadedData, null, 2)}</pre>
|
||||
{/if}
|
||||
```
|
||||
#### Send session to client
|
||||
|
||||
## Server-side data fetching with RLS
|
||||
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.
|
||||
|
||||
```html
|
||||
<!-- 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
|
||||
import type { PageLoad } from './$types'
|
||||
import { redirect } from '@sveltejs/kit'
|
||||
|
||||
export const load: PageLoad = async ({ parent }) => {
|
||||
const { supabase, session } = await parent()
|
||||
if (!session) {
|
||||
throw redirect(303, '/')
|
||||
}
|
||||
const { data: tableData } = await supabase.from('test').select('*')
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```js title=src/routes/+layout.server.js
|
||||
// src/routes/+layout.server.js
|
||||
export const load = async ({ locals: { getSession } }) => {
|
||||
return {
|
||||
user: session.user,
|
||||
tableData,
|
||||
session: await getSession(),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Protecting API routes
|
||||
</TabPanel>
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
```ts title=src/routes/+layout.server.ts
|
||||
// src/routes/+layout.server.ts
|
||||
export const load = async ({ locals: { getSession } }) => {
|
||||
return {
|
||||
session: await getSession(),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</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"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```ts title="src/routes/+layout.js"
|
||||
// src/routes/+layout.js
|
||||
import { PUBLIC_SUPABASE_ANON_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_ANON_KEY,
|
||||
event: { fetch },
|
||||
serverSession: data.session,
|
||||
})
|
||||
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
return { supabase, session }
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
```ts title="src/routes/+layout.ts"
|
||||
// src/routes/+layout.ts
|
||||
import { PUBLIC_SUPABASE_ANON_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_ANON_KEY,
|
||||
event: { fetch },
|
||||
serverSession: data.session,
|
||||
})
|
||||
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
return { supabase, session }
|
||||
}
|
||||
```
|
||||
|
||||
<Admonition type="info">
|
||||
|
||||
TypeScript types can be [generated with the Supabase CLI](https://supabase.com/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: PageData`.
|
||||
|
||||
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 title="src/routes/+layout.svelte"
|
||||
<!-- src/routes/+layout.svelte -->
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
export let 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 title="src/routes/auth/+page.svelte"
|
||||
<!-- // src/routes/auth/+page.svelte -->
|
||||
<script>
|
||||
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"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```js title="src/routes/login/+page.server.js"
|
||||
// src/routes/login/+page.server.js
|
||||
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 title="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 title="src/routes/login/+page.server.ts"
|
||||
// src/routes/login/+page.server.ts
|
||||
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 title="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
|
||||
import type { RequestHandler } from './$types'
|
||||
import { json, error } from '@sveltejs/kit'
|
||||
|
||||
export const GET: RequestHandler = async ({ locals: { supabase, getSession } }) => {
|
||||
export const GET = async ({ locals: { supabase, getSession } }) => {
|
||||
const session = await getSession()
|
||||
if (!session) {
|
||||
// the user is not signed in
|
||||
@@ -266,16 +527,15 @@ export const GET: RequestHandler = async ({ locals: { supabase, getSession } })
|
||||
|
||||
If you visit `/api/protected-route` without a valid session cookie, you will get a 401 response.
|
||||
|
||||
## Protecting Actions
|
||||
### 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
|
||||
import type { Actions } from './$types'
|
||||
import { error, fail } from '@sveltejs/kit'
|
||||
|
||||
export const actions: Actions = {
|
||||
export const actions = {
|
||||
createPost: async ({ request, locals: { supabase, getSession } }) => {
|
||||
const session = await getSession()
|
||||
|
||||
@@ -305,14 +565,148 @@ export const actions: Actions = {
|
||||
|
||||
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 use the handle hook to
|
||||
protect multiple routes at once.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```js
|
||||
// src/hooks.server.js
|
||||
import { redirect, error } from '@sveltejs/kit'
|
||||
|
||||
export const handle = async ({ event, resolve }) => {
|
||||
// protect requests to all routes that start with /protected-routes
|
||||
if (event.url.pathname.startsWith('/protected-routes')) {
|
||||
const session = await event.locals.getSession()
|
||||
if (!session) {
|
||||
// the user is not signed in
|
||||
throw 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.getSession()
|
||||
if (!session) {
|
||||
// the user is not signed in
|
||||
throw error(303, '/')
|
||||
}
|
||||
}
|
||||
|
||||
return resolve(event)
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
```ts
|
||||
// src/hooks.server.ts
|
||||
import { type Handle, redirect, error } from '@sveltejs/kit'
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
// protect requests to all routes that start with /protected-routes
|
||||
if (event.url.pathname.startsWith('/protected-routes')) {
|
||||
const session = await event.locals.getSession()
|
||||
if (!session) {
|
||||
// the user is not signed in
|
||||
throw 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.getSession()
|
||||
if (!session) {
|
||||
// the user is not signed in
|
||||
throw error(303, '/')
|
||||
}
|
||||
}
|
||||
|
||||
return resolve(event)
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Data fetching
|
||||
|
||||
### Client-side data fetching with RLS
|
||||
|
||||
For [row level security](https://supabase.com/docs/guides/auth/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:
|
||||
|
||||
```html
|
||||
<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
|
||||
|
||||
```html
|
||||
<!-- 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
|
||||
import { redirect } from '@sveltejs/kit'
|
||||
|
||||
export const load = async ({ parent }) => {
|
||||
const { supabase, session } = await parent()
|
||||
if (!session) {
|
||||
throw redirect(303, '/')
|
||||
}
|
||||
const { data: tableData } = await supabase.from('test').select('*')
|
||||
|
||||
return {
|
||||
user: session.user,
|
||||
tableData,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Saving and deleting the session
|
||||
|
||||
```ts
|
||||
import type { Actions } from './$types'
|
||||
import { fail, redirect } from '@sveltejs/kit'
|
||||
import { AuthApiError } from '@supabase/supabase-js'
|
||||
|
||||
export const actions: Actions = {
|
||||
export const actions = {
|
||||
signin: async ({ request, locals: { supabase } }) => {
|
||||
const formData = await request.formData()
|
||||
|
||||
@@ -351,42 +745,33 @@ export const actions: Actions = {
|
||||
}
|
||||
```
|
||||
|
||||
## Protecting multiple routes
|
||||
## Migration Guide [#migration]
|
||||
|
||||
To avoid writing the same auth logic in every single route you can use the handle hook to
|
||||
protect multiple routes at once.
|
||||
### 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
|
||||
// src/hooks.server.ts
|
||||
import type { RequestHandler } from './$types'
|
||||
import { redirect, error } from '@sveltejs/kit'
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
// protect requests to all routes that start with /protected-routes
|
||||
if (event.url.pathname.startsWith('/protected-routes')) {
|
||||
const session = await event.locals.getSession()
|
||||
if (!session) {
|
||||
// the user is not signed in
|
||||
throw 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.getSession()
|
||||
if (!session) {
|
||||
// the user is not signed in
|
||||
throw error(303, '/')
|
||||
}
|
||||
}
|
||||
|
||||
return resolve(event)
|
||||
}
|
||||
await supabase.auth.signUp({
|
||||
email: 'jon@example.com',
|
||||
password: 'sup3rs3cur3',
|
||||
options: {
|
||||
emailRedirectTo: 'http://localhost:3000/auth/callback',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Migrate from 0.8.x to 0.9 [#migration]
|
||||
### Migrate from 0.8.x to 0.9 [#migration-0-9]
|
||||
|
||||
### Set up the Supabase client [#migration-set-up-supabase-client]
|
||||
#### 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.
|
||||
|
||||
@@ -448,7 +833,7 @@ export const handle: Handle = async ({ event, resolve }) => {
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Initialize the client [#migration-initialize-client]
|
||||
#### 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.
|
||||
|
||||
@@ -542,11 +927,11 @@ export const load: LayoutLoad = async ({ fetch, data, depends }) => {
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Set up hooks [#migration-set-up-hooks]
|
||||
#### 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.
|
||||
|
||||
### Typings [#migration-typings]
|
||||
#### Typings [#migration-typings]
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -604,7 +989,7 @@ declare global {
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Protecting a page [#migration-protecting-a-page]
|
||||
#### Protecting a page [#migration-protecting-a-page]
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -686,7 +1071,7 @@ export const load: PageLoad = async ({ parent }) => {
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Protecting a API route [#migration-protecting-a-api-route]
|
||||
#### Protecting a API route [#migration-protecting-a-api-route]
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -736,9 +1121,9 @@ export const GET: RequestHandler = async ({ locals: { supabase, getSession } })
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Migrate from 0.7.x to 0.8 [#migration-0-8]
|
||||
### Migrate from 0.7.x to 0.8 [#migration-0-8]
|
||||
|
||||
### Set up the Supabase client [#migration-set-up-supabase-client-0-8]
|
||||
#### Set up the Supabase client [#migration-set-up-supabase-client-0-8]
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -786,7 +1171,7 @@ export const supabaseClient = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_S
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Initialize the client [#migration-initialize-client-0-8]
|
||||
#### Initialize the client [#migration-initialize-client-0-8]
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -842,7 +1227,7 @@ export const supabaseClient = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_S
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Set up hooks [#migration-set-up-hooks-0-8]
|
||||
#### Set up hooks [#migration-set-up-hooks-0-8]
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -889,7 +1274,7 @@ import '$lib/db'
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Typings [#migration-typings-0-8]
|
||||
#### Typings [#migration-typings-0-8]
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -946,7 +1331,7 @@ declare namespace App {
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### withPageAuth [#migration-with-page-auth-0-8]
|
||||
#### withPageAuth [#migration-with-page-auth-0-8]
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -1024,7 +1409,7 @@ export const load: PageLoad = async (event) => {
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### withApiAuth [#migration-with-api-auth-0-8]
|
||||
#### withApiAuth [#migration-with-api-auth-0-8]
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -1077,15 +1462,15 @@ export const GET: RequestHandler = async (event) => {
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Migrate from 0.6.11 and below to 0.7.0 [#migration-0-7]
|
||||
### 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
|
||||
#### 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]
|
||||
#### Set up the Supabase client [#migration-set-up-supabase-client-0-7]
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -1134,7 +1519,7 @@ setupSupabaseHelpers({
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Initialize the client [#migration-initialize-client-0-7]
|
||||
#### Initialize the client [#migration-initialize-client-0-7]
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -1182,7 +1567,7 @@ The `@supabase/auth-helpers-svelte` library is no longer required as the `@supab
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Set up hooks [#migration-set-up-hooks-0-7]
|
||||
#### Set up hooks [#migration-set-up-hooks-0-7]
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -1236,7 +1621,7 @@ export const handle = sequence(auth(), yourHandler)
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Typings [#migration-typings-0-7]
|
||||
#### Typings [#migration-typings-0-7]
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -1293,7 +1678,7 @@ declare namespace App {
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Check the user on the client
|
||||
#### Check the user on the client
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -1335,7 +1720,7 @@ declare namespace App {
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### withPageAuth
|
||||
#### withPageAuth
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -1411,7 +1796,7 @@ export const load: PageLoad = withAuth(async ({ session, getSupabaseClient }) =>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### withApiAuth
|
||||
#### withApiAuth
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
|
||||
Reference in new issue
Block a user