Auth

Supabase Auth with SvelteKit

This submodule provides convenience helpers for implementing user authentication in SvelteKit applications.

Configuration

Install SvelteKit Auth helpers library

This library supports Node.js ^16.15.0.

Terminal

_10
npm install @supabase/auth-helpers-sveltekit @supabase/supabase-js

Declare environment variables

Retrieve your project's URL and anon key from your API settings, and create a .env.local file with the following environment variables:

.env.local

_10
# Find these in your Supabase project settings https://supabase.com/dashboard/project/_/settings/api
_10
PUBLIC_SUPABASE_URL=https://your-project.supabase.co
_10
PUBLIC_SUPABASE_ANON_KEY=your-anon-key

Creating a Supabase client

Create a new hooks.server.js file in the root of your project and populate with the following to retrieve the user session.

The session information from getSession is supplied to the Supabase client so it can retrieve the auth token. This is safe, since the auth token signature will be revalidated on the Auth server. But you shouldn't trust the unsigned data that is stored alongside the auth token:

src/hooks.server.js

_29
// src/hooks.server.js
_29
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'
_29
import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit'
_29
_29
export const handle = async ({ event, resolve }) => {
_29
event.locals.supabase = createSupabaseServerClient({
_29
supabaseUrl: PUBLIC_SUPABASE_URL,
_29
supabaseKey: PUBLIC_SUPABASE_ANON_KEY,
_29
event,
_29
})
_29
_29
/**
_29
* a little helper that is written for convenience so that instead
_29
* of calling `const { data: { session } } = await supabase.auth.getSession()`
_29
* you just call this `await getSession()`
_29
*/
_29
event.locals.getSession = async () => {
_29
const {
_29
data: { session },
_29
} = await event.locals.supabase.auth.getSession()
_29
return session
_29
}
_29
_29
return resolve(event, {
_29
filterSerializedResponseHeaders(name) {
_29
return name === 'content-range'
_29
},
_29
})
_29
}

Code Exchange route

The Code Exchange route is required for the server-side auth flow 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:

src/routes/auth/callback/+server.js

_11
import { redirect } from '@sveltejs/kit'
_11
_11
export const GET = async ({ url, locals: { supabase } }) => {
_11
const code = url.searchParams.get('code')
_11
_11
if (code) {
_11
await supabase.auth.exchangeCodeForSession(code)
_11
}
_11
_11
redirect(303, '/')
_11
}

Generate types from your database

In order to get the most out of TypeScript and it's 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 after you have logged in, linked, and generated types through the Supabase CLI.

src/app.d.ts

_18
// src/app.d.ts
_18
_18
import { SupabaseClient, Session } from '@supabase/supabase-js'
_18
import { Database } from './DatabaseDefinitions'
_18
_18
declare global {
_18
namespace App {
_18
interface Locals {
_18
supabase: SupabaseClient<Database>
_18
getSession(): Promise<Session | null>
_18
}
_18
interface PageData {
_18
session: Session | null
_18
}
_18
// interface Error {}
_18
// interface Platform {}
_18
}
_18
}

Authentication

Authentication can be initiated client or server-side. All of the supabase-js authentication strategies are supported with the Auth Helpers client.

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.

src/routes/+layout.server.js

_10
// src/routes/+layout.server.js
_10
export const load = async ({ locals: { getSession } }) => {
_10
return {
_10
session: await getSession(),
_10
}
_10
}

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.

src/routes/+layout.js

_20
// src/routes/+layout.js
_20
import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'
_20
import { createSupabaseLoadClient } from '@supabase/auth-helpers-sveltekit'
_20
_20
export const load = async ({ fetch, data, depends }) => {
_20
depends('supabase:auth')
_20
_20
const supabase = createSupabaseLoadClient({
_20
supabaseUrl: PUBLIC_SUPABASE_URL,
_20
supabaseKey: PUBLIC_SUPABASE_ANON_KEY,
_20
event: { fetch },
_20
serverSession: data.session,
_20
})
_20
_20
const {
_20
data: { session },
_20
} = await supabase.auth.getSession()
_20
_20
return { supabase, session }
_20
}

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.

src/routes/+layout.svelte

_24
<!-- src/routes/+layout.svelte -->
_24
<script lang="ts">
_24
import { invalidate } from '$app/navigation'
_24
import { onMount } from 'svelte'
_24
_24
export let data
_24
_24
let { supabase, session } = data
_24
$: ({ supabase, session } = data)
_24
_24
onMount(() => {
_24
const {
_24
data: { subscription },
_24
} = supabase.auth.onAuthStateChange((event, _session) => {
_24
if (_session?.expires_at !== session?.expires_at) {
_24
invalidate('supabase:auth')
_24
}
_24
})
_24
_24
return () => subscription.unsubscribe()
_24
});
_24
</script>
_24
_24
<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.

src/routes/auth/+page.svelte

_39
<!-- // src/routes/auth/+page.svelte -->
_39
<script>
_39
export let data
_39
let { supabase } = data
_39
$: ({ supabase } = data)
_39
_39
let email
_39
let password
_39
_39
const handleSignUp = async () => {
_39
await supabase.auth.signUp({
_39
email,
_39
password,
_39
options: {
_39
emailRedirectTo: `${location.origin}/auth/callback`,
_39
},
_39
})
_39
}
_39
_39
const handleSignIn = async () => {
_39
await supabase.auth.signInWithPassword({
_39
email,
_39
password,
_39
})
_39
}
_39
_39
const handleSignOut = async () => {
_39
await supabase.auth.signOut()
_39
}
_39
</script>
_39
_39
<form on:submit="{handleSignUp}">
_39
<input name="email" bind:value="{email}" />
_39
<input type="password" name="password" bind:value="{password}" />
_39
<button>Sign up</button>
_39
</form>
_39
_39
<button on:click="{handleSignIn}">Sign in</button>
_39
<button on:click="{handleSignOut}">Sign out</button>

Server-side

Form Actions can be used to trigger the authentication process from form submissions.

src/routes/login/+page.server.js

_27
// src/routes/login/+page.server.js
_27
import { fail } from '@sveltejs/kit'
_27
_27
export const actions = {
_27
default: async ({ request, url, locals: { supabase } }) => {
_27
const formData = await request.formData()
_27
const email = formData.get('email')
_27
const password = formData.get('password')
_27
_27
const { error } = await supabase.auth.signUp({
_27
email,
_27
password,
_27
options: {
_27
emailRedirectTo: `${url.origin}/auth/callback`,
_27
},
_27
})
_27
_27
if (error) {
_27
return fail(500, { message: 'Server error. Try again later.', success: false, email })
_27
}
_27
_27
return {
_27
message: 'Please check your email for a magic link to log into the website.',
_27
success: true,
_27
}
_27
},
_27
}

src/routes/login/+page.svelte

_11
<!-- // src/routes/login/+page.svelte -->
_11
<script>
_11
import { enhance } from '$app/forms'
_11
export let form
_11
</script>
_11
_11
<form method="post" use:enhance>
_11
<input name="email" value={form?.email ?? ''} />
_11
<input type="password" name="password" />
_11
<button>Sign up</button>
_11
</form>

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.

src/routes/api/protected-route/+server.ts

_13
// src/routes/api/protected-route/+server.ts
_13
import { json, error } from '@sveltejs/kit'
_13
_13
export const GET = async ({ locals: { supabase, getSession } }) => {
_13
const session = await getSession()
_13
if (!session) {
_13
// the user is not signed in
_13
throw error(401, { message: 'Unauthorized' })
_13
}
_13
const { data } = await supabase.from('test').select('*')
_13
_13
return json({ data })
_13
}

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.

src/routes/posts/+page.server.ts

_29
// src/routes/posts/+page.server.ts
_29
import { error, fail } from '@sveltejs/kit'
_29
_29
export const actions = {
_29
createPost: async ({ request, locals: { supabase, getSession } }) => {
_29
const session = await getSession()
_29
_29
if (!session) {
_29
// the user is not signed in
_29
throw error(401, { message: 'Unauthorized' })
_29
}
_29
// we are save, let the user create the post
_29
const formData = await request.formData()
_29
const content = formData.get('content')
_29
_29
const { error: createPostError, data: newPost } = await supabase
_29
.from('posts')
_29
.insert({ content })
_29
_29
if (createPostError) {
_29
return fail(500, {
_29
supabaseErrorMessage: createPostError.message,
_29
})
_29
}
_29
return {
_29
newPost,
_29
}
_29
},
_29
}

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 function. Edit your /src/hooks.server.js with the below:

src/hooks.server.js

_55
// src/hooks.server.js
_55
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'
_55
import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit'
_55
import { redirect, error } from '@sveltejs/kit'
_55
import { sequence } from '@sveltejs/kit/hooks'
_55
_55
async function supabase({ event, resolve }) {
_55
event.locals.supabase = createSupabaseServerClient({
_55
supabaseUrl: PUBLIC_SUPABASE_URL,
_55
supabaseKey: PUBLIC_SUPABASE_ANON_KEY,
_55
event,
_55
})
_55
_55
/**
_55
* a little helper that is written for convenience so that instead
_55
* of calling `const { data: { session } } = await supabase.auth.getSession()`
_55
* you just call this `await getSession()`
_55
*/
_55
event.locals.getSession = async () => {
_55
const {
_55
data: { session },
_55
} = await event.locals.supabase.auth.getSession()
_55
return session
_55
}
_55
_55
return resolve(event, {
_55
filterSerializedResponseHeaders(name) {
_55
return name === 'content-range'
_55
},
_55
})
_55
}
_55
_55
async function authorization({ event, resolve }) {
_55
// protect requests to all routes that start with /protected-routes
_55
if (event.url.pathname.startsWith('/protected-routes') && event.request.method === 'GET') {
_55
const session = await event.locals.getSession()
_55
if (!session) {
_55
// the user is not signed in
_55
redirect(303, '/')
_55
}
_55
}
_55
_55
// protect POST requests to all routes that start with /protected-posts
_55
if (event.url.pathname.startsWith('/protected-posts') && event.request.method === 'POST') {
_55
const session = await event.locals.getSession()
_55
if (!session) {
_55
// the user is not signed in
_55
throw error(303, '/')
_55
}
_55
}
_55
_55
return resolve(event)
_55
}
_55
_55
export const handle = sequence(supabase, authorization)

Data fetching

Client-side data fetching with RLS

For 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:

src/routes/+page.svelte

_18
<script lang="ts">
_18
export let data
_18
_18
let loadedData = []
_18
async function loadData() {
_18
const { data: result } = await data.supabase.from('test').select('*').limit(20)
_18
loadedData = result
_18
}
_18
_18
$: if (data.session) {
_18
loadData()
_18
}
_18
</script>
_18
_18
{#if data.session}
_18
<p>client-side data fetching with RLS</p>
_18
<pre>{JSON.stringify(loadedData, null, 2)}</pre>
_18
{/if}

Server-side data fetching with RLS

src/routes/profile/+page.svelte

_11
<!-- src/routes/profile/+page.svelte -->
_11
<script lang="ts">
_11
export let data
_11
_11
let { user, tableData } = data
_11
$: ({ user, tableData } = data)
_11
</script>
_11
_11
<div>Protected content for {user.email}</div>
_11
<pre>{JSON.stringify(tableData, null, 2)}</pre>
_11
<pre>{JSON.stringify(user, null, 2)}</pre>

src/routes/profile/+page.ts

_15
// src/routes/profile/+page.ts
_15
import { redirect } from '@sveltejs/kit'
_15
_15
export const load = async ({ parent }) => {
_15
const { supabase, session } = await parent()
_15
if (!session) {
_15
redirect(303, '/')
_15
}
_15
const { data: tableData } = await supabase.from('test').select('*')
_15
_15
return {
_15
user: session.user,
_15
tableData,
_15
}
_15
}

Saving and deleting the session


_40
import { fail, redirect } from '@sveltejs/kit'
_40
import { AuthApiError } from '@supabase/supabase-js'
_40
_40
export const actions = {
_40
signin: async ({ request, locals: { supabase } }) => {
_40
const formData = await request.formData()
_40
_40
const email = formData.get('email') as string
_40
const password = formData.get('password') as string
_40
_40
const { error } = await supabase.auth.signInWithPassword({
_40
email,
_40
password,
_40
})
_40
_40
if (error) {
_40
if (error instanceof AuthApiError && error.status === 400) {
_40
return fail(400, {
_40
error: 'Invalid credentials.',
_40
values: {
_40
email,
_40
},
_40
})
_40
}
_40
return fail(500, {
_40
error: 'Server error. Try again later.',
_40
values: {
_40
email,
_40
},
_40
})
_40
}
_40
_40
redirect(303, '/dashboard')
_40
},
_40
_40
signout: async ({ locals: { supabase } }) => {
_40
await supabase.auth.signOut()
_40
redirect(303, '/')
_40
},
_40
}

Migration guide

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 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:


_10
await supabase.auth.signUp({
_10
_10
password: 'sup3rs3cur3',
_10
options: {
_10
emailRedirectTo: 'http://localhost:3000/auth/callback',
_10
},
_10
})

Migrate from 0.8.x to 0.9

Set up the Supabase client

In version 0.9 we now setup our Supabase client for the server inside of a hooks.server.ts file.

src/lib/db.ts

_10
// src/lib/db.ts
_10
import { createClient } from '@supabase/auth-helpers-sveltekit'
_10
import { env } from '$env/dynamic/public'
_10
// or use the static env
_10
_10
// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public';
_10
_10
export const supabaseClient = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_SUPABASE_ANON_KEY)

Initialize the 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.

src/routes/+layout.svelte

_20
<!-- src/routes/+layout.svelte -->
_20
<script lang="ts">
_20
import { supabaseClient } from '$lib/db'
_20
import { invalidate } from '$app/navigation'
_20
import { onMount } from 'svelte'
_20
_20
onMount(() => {
_20
const {
_20
data: { subscription },
_20
} = supabaseClient.auth.onAuthStateChange(() => {
_20
invalidate('supabase:auth')
_20
})
_20
_20
return () => {
_20
subscription.unsubscribe()
_20
}
_20
})
_20
</script>
_20
_20
<slot />

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

src/app.d.ts

_19
// src/app.d.ts
_19
/// <reference types="@sveltejs/kit" />
_19
_19
// See https://kit.svelte.dev/docs/types#app
_19
// for information about these interfaces
_19
// and what to do when importing types
_19
declare namespace App {
_19
interface Supabase {
_19
Database: import('./DatabaseDefinitions').Database
_19
SchemaName: 'public'
_19
}
_19
_19
// interface Locals {}
_19
interface PageData {
_19
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession
_19
}
_19
// interface Error {}
_19
// interface Platform {}
_19
}

Protecting a page

src/routes/profile/+page.svelte

_10
<!-- src/routes/profile/+page.svelte -->
_10
<script lang="ts">
_10
/** @type {import('./$types').PageData} */
_10
export let data
_10
$: ({ user, tableData } = data)
_10
</script>
_10
_10
<div>Protected content for {user.email}</div>
_10
<pre>{JSON.stringify(tableData, null, 2)}</pre>
_10
<pre>{JSON.stringify(user, null, 2)}</pre>

src/routes/profile/+page.ts

_17
// src/routes/profile/+page.ts
_17
import type { PageLoad } from './$types'
_17
import { getSupabase } from '@supabase/auth-helpers-sveltekit'
_17
import { redirect } from '@sveltejs/kit'
_17
_17
export const load: PageLoad = async (event) => {
_17
const { session, supabaseClient } = await getSupabase(event)
_17
if (!session) {
_17
redirect(303, '/')
_17
}
_17
const { data: tableData } = await supabaseClient.from('test').select('*')
_17
_17
return {
_17
user: session.user,
_17
tableData,
_17
}
_17
}

Protecting a API route

src/routes/api/protected-route/+server.ts

_14
// src/routes/api/protected-route/+server.ts
_14
import type { RequestHandler } from './$types'
_14
import { getSupabase } from '@supabase/auth-helpers-sveltekit'
_14
import { json, redirect } from '@sveltejs/kit'
_14
_14
export const GET: RequestHandler = async (event) => {
_14
const { session, supabaseClient } = await getSupabase(event)
_14
if (!session) {
_14
redirect(303, '/')
_14
}
_14
const { data } = await supabaseClient.from('test').select('*')
_14
_14
return json({ data })
_14
}

Migrate from 0.7.x to 0.8

Set up the Supabase client

src/lib/db.ts

_19
import { createClient } from '@supabase/supabase-js'
_19
import { setupSupabaseHelpers } from '@supabase/auth-helpers-sveltekit'
_19
import { dev } from '$app/environment'
_19
import { env } from '$env/dynamic/public'
_19
// or use the static env
_19
_19
// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public';
_19
_19
export const supabaseClient = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_SUPABASE_ANON_KEY, {
_19
persistSession: false,
_19
autoRefreshToken: false,
_19
})
_19
_19
setupSupabaseHelpers({
_19
supabaseClient,
_19
cookieOptions: {
_19
secure: !dev,
_19
},
_19
})

Initialize the client

src/routes/+layout.svelte

_15
<script lang="ts">
_15
// make sure the supabase instance is initialized on the client
_15
import '$lib/db'
_15
import { startSupabaseSessionSync } from '@supabase/auth-helpers-sveltekit'
_15
import { page } from '$app/stores'
_15
import { invalidateAll } from '$app/navigation'
_15
_15
// this sets up automatic token refreshing
_15
startSupabaseSessionSync({
_15
page,
_15
handleRefresh: () => invalidateAll(),
_15
})
_15
</script>
_15
_15
<slot />

Set up hooks

src/hooks.server.ts

_10
// make sure the supabase instance is initialized on the server
_10
import '$lib/db'
_10
import { dev } from '$app/environment'
_10
import { auth } from '@supabase/auth-helpers-sveltekit/server'
_10
_10
export const handle = auth()

Optional if using additional handle methods

src/hooks.server.ts

_10
// make sure the supabase instance is initialized on the server
_10
import '$lib/db'
_10
import { dev } from '$app/environment'
_10
import { auth } from '@supabase/auth-helpers-sveltekit/server'
_10
import { sequence } from '@sveltejs/kit/hooks'
_10
_10
export const handle = sequence(auth(), yourHandler)

Typings

src/app.d.ts

_17
/// <reference types="@sveltejs/kit" />
_17
_17
// See https://kit.svelte.dev/docs/types#app
_17
// for information about these interfaces
_17
// and what to do when importing types
_17
declare namespace App {
_17
interface Locals {
_17
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession
_17
}
_17
_17
interface PageData {
_17
session: import('@supabase/auth-helpers-sveltekit').SupabaseSession
_17
}
_17
_17
// interface Error {}
_17
// interface Platform {}
_17
}

withPageAuth

src/routes/protected-route/+page.svelte

_12
<script lang="ts">
_12
import type { PageData } from './$types'
_12
_12
export let data: PageData
_12
$: ({ tableData, user } = data)
_12
</script>
_12
_12
<div>Protected content for {user.email}</div>
_12
<p>server-side fetched data with RLS:</p>
_12
<pre>{JSON.stringify(tableData, null, 2)}</pre>
_12
<p>user:</p>
_12
<pre>{JSON.stringify(user, null, 2)}</pre>

src/routes/protected-route/+page.ts

_12
import { withAuth } from '@supabase/auth-helpers-sveltekit'
_12
import { redirect } from '@sveltejs/kit'
_12
import type { PageLoad } from './$types'
_12
_12
export const load: PageLoad = withAuth(async ({ session, getSupabaseClient }) => {
_12
if (!session.user) {
_12
redirect(303, '/')
_12
}
_12
_12
const { data: tableData } = await getSupabaseClient().from('test').select('*')
_12
return { tableData, user: session.user }
_12
})

withApiAuth

src/routes/api/protected-route/+server.ts

_18
import type { RequestHandler } from './$types'
_18
import { withAuth } from '@supabase/auth-helpers-sveltekit'
_18
import { json, redirect } from '@sveltejs/kit'
_18
_18
interface TestTable {
_18
id: string
_18
created_at: string
_18
}
_18
_18
export const GET: RequestHandler = withAuth(async ({ session, getSupabaseClient }) => {
_18
if (!session.user) {
_18
redirect(303, '/')
_18
}
_18
_18
const { data } = await getSupabaseClient().from<TestTable>('test').select('*')
_18
_18
return json({ data })
_18
})

Migrate from 0.6.11 and below to 0.7.0

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

src/lib/db.ts

_10
import { createSupabaseClient } from '@supabase/auth-helpers-sveltekit';
_10
_10
const { supabaseClient } = createSupabaseClient(
_10
import.meta.env.VITE_SUPABASE_URL as string,
_10
import.meta.env.VITE_SUPABASE_ANON_KEY as string
_10
);
_10
_10
export { supabaseClient };

Initialize the client

src/routes/__layout.svelte

_10
<script>
_10
import { session } from '$app/stores'
_10
import { supabaseClient } from '$lib/db'
_10
import { SupaAuthHelper } from '@supabase/auth-helpers-svelte'
_10
</script>
_10
_10
<SupaAuthHelper {supabaseClient} {session}>
_10
<slot />
_10
</SupaAuthHelper>

Set up hooks

src/hooks.ts

_14
import { handleAuth } from '@supabase/auth-helpers-sveltekit'
_14
import type { GetSession, Handle } from '@sveltejs/kit'
_14
import { sequence } from '@sveltejs/kit/hooks'
_14
_14
export const handle: Handle = sequence(...handleAuth())
_14
_14
export const getSession: GetSession = async (event) => {
_14
const { user, accessToken, error } = event.locals
_14
return {
_14
user,
_14
accessToken,
_14
error,
_14
}
_14
}

Typings

src/app.d.ts

_18
/// <reference types="@sveltejs/kit" />
_18
// See https://kit.svelte.dev/docs/types#app
_18
// for information about these interfaces
_18
declare namespace App {
_18
interface UserSession {
_18
user: import('@supabase/supabase-js').User
_18
accessToken?: string
_18
}
_18
_18
interface Locals extends UserSession {
_18
error: import('@supabase/supabase-js').ApiError
_18
}
_18
_18
interface Session extends UserSession {}
_18
_18
// interface Platform {}
_18
// interface Stuff {}
_18
}

Check the user on the client

src/routes/index.svelte

_10
<script>
_10
import { session } from '$app/stores'
_10
</script>
_10
_10
{#if !$session.user}
_10
<h1>I am not logged in</h1>
_10
{:else}
_10
<h1>Welcome {$session.user.email}</h1>
_10
<p>I am logged in!</p>
_10
{/if}

withPageAuth

src/routes/protected-route.svelte

_27
<script lang="ts" context="module">
_27
import { supabaseServerClient, withPageAuth } from '@supabase/auth-helpers-sveltekit'
_27
import type { Load } from './__types/protected-page'
_27
_27
export const load: Load = async ({ session }) =>
_27
withPageAuth(
_27
{
_27
redirectTo: '/',
_27
user: session.user,
_27
},
_27
async () => {
_27
const { data } = await supabaseServerClient(session.accessToken).from('test').select('*')
_27
return { props: { data, user: session.user } }
_27
}
_27
)
_27
</script>
_27
_27
<script>
_27
export let data
_27
export let user
_27
</script>
_27
_27
<div>Protected content for {user.email}</div>
_27
<p>server-side fetched data with RLS:</p>
_27
<pre>{JSON.stringify(data, null, 2)}</pre>
_27
<p>user:</p>
_27
<pre>{JSON.stringify(user, null, 2)}</pre>

withApiAuth

src/routes/api/protected-route.ts

_22
import { supabaseServerClient, withApiAuth } from '@supabase/auth-helpers-sveltekit'
_22
import type { RequestHandler } from './__types/protected-route'
_22
_22
interface TestTable {
_22
id: string
_22
created_at: string
_22
}
_22
_22
interface GetOutput {
_22
data: TestTable[]
_22
}
_22
_22
export const GET: RequestHandler<GetOutput> = async ({ locals, request }) =>
_22
withApiAuth({ user: locals.user }, async () => {
_22
// Run queries with RLS on the server
_22
const { data } = await supabaseServerClient(request).from('test').select('*')
_22
_22
return {
_22
status: 200,
_22
body: { data },
_22
}
_22
})