@rychunpm/auth-helpers-sveltekit v0.6.14
@supabase/auth-helpers-sveltekit (BETA)
This submodule provides convenience helpers for implementing user authentication in SvelteKit applications.
Installation
Using npm:
npm install @supabase/auth-helpers-sveltekitUsing yarn:
yarn add @supabase/auth-helpers-sveltekitThis library supports the following tooling versions:
- Node.js:
^16.15.0
Getting Started
Configuration
Set up the fillowing env vars. For local development you can set them in a .env file. See an example here.
# Find these in your Supabase project settings > API
PUBLIC_SUPABASE_URL=https://your-project.supabase.co
PUBLIC_SUPABASE_ANON_KEY=your-anon-keySupabaseClient setup
We will start off by creating a db.ts file inside of our src/lib directory. Now lets instantiate our supabaseClient.
// 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_ANON_KEY } from '$env/static/public';
export const supabaseClient = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_SUPABASE_ANON_KEY, {
persistSession: false,
autoRefreshToken: false
});
setupSupabaseHelpers({
supabaseClient,
cookieOptions: {
secure: !dev
}
});Initialize the client
Edit your +layout.svelte file and set up the client side.
<!-- src/routes/+layout.svelte -->
<script lang="ts">
// we need to make sure the supabase instance is initialized on the client
import '$lib/db';
import { startSupabaseSessionSync } from '@supabase/auth-helpers-sveltekit';
// this sets up automatic token refreshing
startSupabaseSessionSync();
</script>
<slot />Hooks setup
Our hooks.ts file is where the heavy lifting of this library happens:
// src/hooks.server.ts
// we need to 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();
// use the sequence helper if you have additional Handle methods
import { sequence } from '@sveltejs/kit/hooks';
export const handle = sequence(auth(), yourHandler);There are three handle methods available:
callback():This will create a handler for
/api/auth/callback. Theclientforwards the session details here every timeonAuthStateChangefires on the client side. This is needed to set up the cookies for your application so that SSR works seamlessly.session():This will parse the session from the cookie and populate it in locals
auth():a shorthand for
sequence(callback(), session())that uses both handlers
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 load function:
// src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ locals }) => {
return {
session: locals.session
};
};Typings
In order to get the most out of TypeScript and it´s intellisense, you should import our types into the app.d.ts type definition file that comes with your SvelteKit project.
// 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 {}
}Basic Setup
You can now determine if a user is authenticated on the client-side by checking that the user object in $page.data.session is defined.
<!-- 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}Client-side data fetching with RLS
For row level security to work properly when fetching data client-side, you need to make sure to import the { supabaseClient } from $lib/db and only run your query once the user is defined client-side in $page.data.session:
<script>
import { supabaseClient } from '$lib/db';
import { page } from '$app/stores';
let loadedData = [];
async function loadData() {
const { data } = await supabaseClient.from('test').select('*').limit(20);
loadedData = data;
}
$: if ($page.data.session.user) {
loadData();
}
</script>
{#if $page.data.session.user}
<p>client-side data fetching with RLS</p>
<pre>{JSON.stringify(loadedData, null, 2)}</pre>
{/if}Server-side data fetching with RLS
<!-- src/routes/profile/+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>For row level security to work in a server environment, you need to use the withAuth helper to check if the user is authenticated. The helper extends the event with session and getSupabaseClient():
// src/routes/profile/+page.ts
import type { PageLoad } from './$types';
import { withAuth } from '@supabase/auth-helpers-sveltekit';
import { redirect } from '@sveltejs/kit';
interface TestTable {
id: string;
created_at: string;
}
export const load: PageLoad = withAuth(async ({ getSupabaseClient, session }) => {
if (!session.user) {
throw redirect(303, '/');
}
const { data: tableData } = await getSupabaseClient()
.from<TestTable>('test')
.select('*');
return {
user: session.user,
tableData
};
);Caution:
Always use the instance returned by getSupabaseClient() directly!
// Bad
const supabaseClient = getSupabaseClient();
await supabaseClient.from('table1').select();
await supabaseClient.from('table2').select();
// Good
await getSupabaseClient().from('table1').select();
await getSupabaseClient().from('table2').select();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
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) {
throw redirect(303, '/');
}
const { data } = await getSupabaseClient()
.from<TestTable>('test')
.select('*');
return json({ data });
);If you visit /api/protected-route without a valid session cookie, you will get a 303 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
import type { Actions } from './$types';
import { withAuth } from '@supabase/auth-helpers-sveltekit';
import { error, invalid } from '@sveltejs/kit';
export const actions: Actions = {
createPost: withAuth(async ({ session, getSupabaseClient, request }) => {
if (!session.user) {
// the user is not signed in
throw error(403, { 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 getSupabaseClient()
.from('posts')
.insert({ content });
if (createPostError) {
return invalid(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 403 error response.
Saving and deleting the session
Use saveSession to save the session cookies:
import type { Actions } from './$types';
import { supabaseClient } from '$lib/db';
import { invalid, redirect } from '@sveltejs/kit';
import { saveSession } from '@supabase/auth-helpers-sveltekit/server';
export const actions: Actions = {
async signin({ request, cookies, url }) {
const formData = await request.formData();
const email = formData.get('email') as string;
const password = formData.get('password') as string;
const { data, error } = await supabaseClient.auth.api.signInWithEmail(
email,
password,
{
redirectTo: `${url.origin}/logging-in`
}
);
if (error || !data) {
if (error?.status === 400) {
return invalid(400, {
error: 'Invalid credentials',
values: {
email
}
});
}
return invalid(500, {
error: 'Server error. Try again later.',
values: {
email
}
});
}
saveSession(cookies, data);
throw redirect(303, '/dashboard');
}
};Use deleteSession to delete the session cookies:
import type { Actions } from './$types';
import { deleteSession } from '@supabase/auth-helpers-sveltekit/server';
import { redirect } from '@sveltejs/kit';
export const actions: Actions = {
async logout({ cookies }) {
deleteSession(cookies);
throw redirect(303, '/');
}
};Custom session namespace
If you wan´t to use something else than locals.session and $page.data.session you can do so by updating the types and creating three helper functions:
// src/app.d.ts
declare namespace App {
interface Locals {
mySupabaseSession: import('@supabase/auth-helpers-sveltekit').SupabaseSession;
}
interface PageData {
mySupabaseSession: import('@supabase/auth-helpers-sveltekit').SupabaseSession;
}
}
// src/hooks.server.ts
setupSupabaseServer({
supabaseClient,
cookieOptions: {
secure: !dev
},
// --- change location within locals ---
getSessionFromLocals: (locals) => locals.mySupabaseSession,
setSessionToLocals: (locals, session) => (locals.mySupabaseSession = session)
});
// src/lib/db.ts
setupSupabaseClient({
supabaseClient,
// --- change location within pageData ---
getSessionFromPageData: (data) => data.mySupabaseSession
});