diff --git a/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx b/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx
index 13e109beae9..e279e528a5b 100644
--- a/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx
+++ b/apps/docs/pages/guides/auth/auth-helpers/sveltekit.mdx
@@ -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:
+
+
+
+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'
+ },
+ })
+}
+```
+
+
+
+
+
+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.
+
+
-### Send session to client
+
-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'
+
-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.
+
+
+
+
+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
+
-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:
+
-```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({
- 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
-
-
-
-
-```
-
-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.
+
+
### 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
-
+### Client-side
-{#if data.session}
-
client-side data fetching with RLS
-
{JSON.stringify(loadedData, null, 2)}
-{/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
-
-
-
-
Protected content for {user.email}
-
{JSON.stringify(tableData, null, 2)}
-
{JSON.stringify(user, null, 2)}
-```
-
-```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('*')
+
+
+```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
+
+
+
+```ts title=src/routes/+layout.server.ts
+// src/routes/+layout.server.ts
+export const load = async ({ locals: { getSession } }) => {
+ return {
+ session: await getSession(),
+ }
+}
+```
+
+
+
+
+#### 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.
+
+
+
+
+```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 }
+}
+```
+
+
+
+
+
+```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({
+ supabaseUrl: PUBLIC_SUPABASE_URL,
+ supabaseKey: PUBLIC_SUPABASE_ANON_KEY,
+ event: { fetch },
+ serverSession: data.session,
+ })
+
+ const {
+ data: { session },
+ } = await supabase.auth.getSession()
+
+ return { supabase, session }
+}
+```
+
+
+
+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.
+
+
+
+
+
+
+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"
+
+
+
+
+```
+
+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"
+
+
+
+
+
+
+
+```
+
+### Server-side
+
+[Form Actions](https://kit.svelte.dev/docs/form-actions) can be used to trigger the authentication process from form submissions.
+
+
+
+
+```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"
+
+
+
+
+```
+
+
+
+
+
+```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"
+
+
+
+
+```
+
+
+
+
+## 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.
+
+
+
+
+```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)
+}
+```
+
+
+
+
+
+```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)
+}
+```
+
+
+
+
+## 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
+
+
+{#if data.session}
+
client-side data fetching with RLS
+
{JSON.stringify(loadedData, null, 2)}
+{/if}
+```
+
+### Server-side data fetching with RLS
+
+```html
+
+
+
+
Protected content for {user.email}
+
{JSON.stringify(tableData, null, 2)}
+
{JSON.stringify(user, null, 2)}
+```
+
+```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 }) => {
-### 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 }) => {
-### 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]
-### Protecting a page [#migration-protecting-a-page]
+#### Protecting a page [#migration-protecting-a-page]
{
-### Protecting a API route [#migration-protecting-a-api-route]
+#### Protecting a API route [#migration-protecting-a-api-route]
-## 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]
-### Initialize the client [#migration-initialize-client-0-8]
+#### Initialize the client [#migration-initialize-client-0-8]
-### Set up hooks [#migration-set-up-hooks-0-8]
+#### Set up hooks [#migration-set-up-hooks-0-8]
-### Typings [#migration-typings-0-8]
+#### Typings [#migration-typings-0-8]
-### withPageAuth [#migration-with-page-auth-0-8]
+#### withPageAuth [#migration-with-page-auth-0-8]
{
-### withApiAuth [#migration-with-api-auth-0-8]
+#### withApiAuth [#migration-with-api-auth-0-8]
{
-## 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]
-### Initialize the client [#migration-initialize-client-0-7]
+#### Initialize the client [#migration-initialize-client-0-7]
-### Set up hooks [#migration-set-up-hooks-0-7]
+#### Set up hooks [#migration-set-up-hooks-0-7]
-### Typings [#migration-typings-0-7]
+#### Typings [#migration-typings-0-7]
-### Check the user on the client
+#### Check the user on the client
-### withPageAuth
+#### withPageAuth
-### withApiAuth
+#### withApiAuth