SvelteKit with Supabase SSR Auth Helpers
Tags: sveltekit, supabase, tutorial, svelte
Published: 2022-07-24 20:25:00

## TL;DR 

[https://github.com/kvetoslavnovak/SvelteKitSupabaseAuthApp](https://github.com/kvetoslavnovak/SvelteKitSupabaseAuthApp)

EDIT: Supabase released new ssr package replacing Auth Helpers. You can follow respective tutorial [here](https://dev.to/kvetoslavnovak/supabase-ssr-auth-48j4).

<br /><br />
## What are Auth Helpers?
Supabase has recently [announced](https://supabase.com/blog/2022/07/13/supabase-auth-helpers-with-sveltekit-support) the revamped auth helpers for Supabase with SvelteKit support.

According to Supabase one of the challenges has been creating a simple experience for server-side rendering (SSR) environments. Auth Helpers are a collection of framework-specific utilities for Supabase Auth. They allow you to implement secure applications with little effort. These libraries include functions for protecting API routes and pages in your applications.

<br />

## How to use Supabase Auth Helpers in SvelteKit Projet?
Just install these two npm packages in your SvelteKit project:
```bash
npm install @supabase/auth-helpers-sveltekit
npm install @supabase/auth-helpers-svelte
```


## Detailed Tutorial

Here is my [github repo of the tutorial](https://github.com/kvetoslavnovak/SvelteKitSupabaseAuthApp).

### Set Up SvelteKit Project
We will try to have a minimal bare bone project. You are free to add any styling, type checking etc later. Supabase published quite nice [examples](https://github.com/supabase-community/auth-helpers/tree/main/examples) using TypeScript and the [documentation](https://github.com/supabase-community/auth-helpers/blob/main/packages/sveltekit/README.md). 

Let name the project SvelteKitSupabaseAuthApp. 
Just use the Skeleton project, no TypeScript, no ESLint, no Prettier, no Playwright.

```bash
npm create svelte@latest SvelteKitSupabaseAuthApp
cd SvelteKitSupabaseAuthApp
npm install
```
<br />
### Install Supabase Auth Helpers for SvelteKit
```bash
npm install @supabase/auth-helpers-sveltekit
npm install @supabase/auth-helpers-svelte
```

### Set Up Supabase Project

- Login in [Supabase](https://app.supabase.com) and create a new project on the Supabase dashboard. 
- Go to Authentication > Settings section and change User Sessions - Site URL from http://localhost:3000 to **http://localhost:5173** and save. This is a localhost address where SvelteKit serves the project in development mode. This change was introduced with Vite 3. 
- Go to Settings > API section and get the URL and anon key of your project.
- Go back to your SvelteKitSupabaseAuthApp project and in its root create a new .env file. Replace VITE_SUPABASE_URL with the URL from previous step and VITE_SUPABASE_ANON_KEY with anon key from previous step:

```bash
# .env
# Update these with your Supabase details from your project settings > API
VITE_SUPABASE_URL=https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY=your-anon-key
```
<br />
### Supabase Client Creation
In src folder create a new folder lib and in this lib folder create a new file sb.js, here we create supabaseClient using our credentials from .env end export the supabaseClient so we can use its auth methods in our application.

We will use login with email in this app. Supabase  sends confirmation email automatically when a user signs ups.

```js
// lib/sb.js
import { createSupabaseClient } from '@supabase/auth-helpers-sveltekit';

const { supabaseClient } = createSupabaseClient(
	import.meta.env.VITE_SUPABASE_URL,
	import.meta.env.VITE_SUPABASE_ANON_KEY
);

export { supabaseClient };

```

<br />
### Hooks
In SvelteKit hook enables us to use two important functions, a handle function and a getSession function. The handle function runs every time the SvelteKit server receives a request. The getSession function takes the event object and returns a session object that is accessible on the client. This enables us to access data concerning user and cookies for example.

In src folder create a new file hooks.js
```js
// src/hooks.js
import { handleAuth } from '@supabase/auth-helpers-sveltekit';
import { sequence } from '@sveltejs/kit/hooks';

export const handle = sequence(
	...handleAuth({
		cookieOptions: { lifetime: 1 * 365 * 24 * 60 * 60 }
	})
);

export const getSession = async (event) => {
	const { user, accessToken } = event.locals;
	return {
		user,
		accessToken
	};
};

```
<br />
### Layout
In src/routes/ folder create a new layout file __layout.svelte so we can have a simple layout but more importantly to introduce our SupaAuthHelper component.

```js
// src/routes/__layout.svelte
<script>
	import { session } from '$app/stores';
	import { supabaseClient } from '$lib/sb';
	import { SupaAuthHelper } from '@supabase/auth-helpers-svelte';
</script>

<svelte:head>
	<title>Email and Password Demo - Supabase Auth Helpers</title>
</svelte:head>

<SupaAuthHelper {supabaseClient} {session}>
	<main>
		<div>
			<h2>
				<a href="/">Supabase Auth Helpers Demo</a>
			</h2>

			<slot />

			<div>
				{#if $session?.user?.id}
					<a href="/api/auth/logout">Sign out</a>
				{/if}
			</div>
		</div>
	</main>
</SupaAuthHelper>

```

<br />
### Sign up Page and Route
In src/routes/ folder create a new file signup,svelte Just a simple sing up form.

```js
// src/routes/signup,svelte
<script>
	export let errors = null;
	export let values = null;
	export let message = null;
</script>

<section>
	<div>
		<h1>Sign up</h1>
		{#if errors}
			<div>{errors.form}</div>
		{/if}
		{#if message}
			<div>{message}</div>
		{/if}
		<form method="post">
			<div>
				<label for="email">Email</label>
				<p>
					<input
						id="email"
						name="email"
						value={values?.email ?? ''}
						type="email"
						placeholder="Email"
						required
					/>
				</p>
			</div>
			<div>
				<label for="password">Password</label>
				<p>
					<input
						id="password"
						name="password"
						value={values?.password ?? ''}
						type="password"
						placeholder="Password"
						required
					/>
				</p>
			</div>
			<div>
				<p>
					<button>Sign up</button>
				</p>
			</div>
		</form>

		<div>
			<p>
				Already have an account? <a href="/">Sign in</a>
			</p>
		</div>
	</div>
</section>

```

In src/routes/ folder create a new signup.js file. This endpoint points the existing user to /dashboard which is protected user only page or provides sing up data to supabaseClient. We are also adding object specifying redirect when client clicks confirmation link in a confirmation email that Supabase will send him/her. 
```js
// src/routes/signup.js
import { supabaseClient } from '$lib/sb';

export const GET = async ({ locals }) => {
	// if the user is already logged in, then redirect to the dashboard
	if (locals.user) {
		return {
			status: 303,
			headers: {
				location: '/dashboard'
			}
		};
	}
	return {
		status: 200
	};
};

export const POST = async ({ request, url }) => {
	const data = await request.formData();

	const email = data.get('email');
	const password = data.get('password');

	const errors = {};
	const values = { email, password };

	const { error } = await supabaseClient.auth.signUp(
		{ email, password }, 
		{ redirectTo: `${url.origin}/logging-in`}
	);

	if (error) {
		errors.form = error.message;
		return {
			status: 400,
			body: {
				errors,
				values
			}
		};
	}

	return {
		status: 200,
		body: {
			message: 'Please check your email for a confirmation email.'
		}
	};
};

```

<br />
### Log in (index) Page and Route
In src/routes/ folder replace index.svelte with this new one.Just a simple login form.
```js
// src/routes/index.svelte
<script>
	export let errors;
	export let values;
</script>

<section>
	<div>
		<h1>Sign in</h1>
		{#if errors}
			<div>{errors.form}</div>
		{/if}
		<form method="post">
			<div>
				<label for="email">Email</label>
				<p>
					<input
						id="email"
						name="email"
						value={values?.email ?? ''}
						type="email"
						placeholder="Email"
						required
					/>
				</p>
			</div>
			<div>
				<label for="password">Password</label>
				<p>
					<input
						id="password"
						name="password"
						value={values?.password ?? ''}
						type="password"
						placeholder="Password"
						required
					/>
				</p>
			</div>
			<div>
				<p>
					<button>Sign in</button>
				</p>
			</div>
		</form>

		<div>
			<p>
				Don't have an account? <a href="/signup">Sign up</a>
			</p>
		</div>
	</div>
</section>

```

In src/routes/ folder create a new index.js file. This endpoint points existing user to /dashboard which is protected user only page or provides login data to supabaseClient, returned session is used for a cookie creation.
```js
//src/routes/index.js
import { supabaseClient } from '$lib/sb';

export const GET = async ({ locals }) => {
	if (locals.user) {
		return {
			status: 303,
			headers: {
				location: '/dashboard'
			}
		};
	}
	return {
		status: 200
	};
};

export const POST = async ({ request, url }) => {
	const data = await request.formData();

	const email = data.get('email');
	const password = data.get('password');

	const headers = { location: '/dashboard' };
	const errors = {};
	const values = { email, password };

	const { session, error } = await supabaseClient.auth.signIn({ email, password });

	if (error) {
		errors.form = error.message;
		return {
			status: 400,
			body: {
				errors,
				values
			}
		};
	}

	if (session) {
		const response = await fetch(`${url.origin}/api/auth/callback`, {
			method: 'POST',
			headers: new Headers({ 'Content-Type': 'application/json' }),
			credentials: 'same-origin',
			body: JSON.stringify({ event: 'SIGNED_IN', session })
		});

		// TODO: Add helper inside of auth-helpers-sveltekit library to manage this better
		const cookies = response.headers
			.get('set-cookie')
			.split('SameSite=Lax, ')
			.map((cookie) => {
				if (!cookie.includes('SameSite=Lax')) {
					cookie += 'SameSite=Lax';
				}
				return cookie;
			});
		headers['Set-Cookie'] = cookies;
	}
	return {
		status: 303,
		headers
	};
};


```

<br />
### Email confirmation redirect page
In src/routes/ folder create a new file logging-in@blank.svelte so we can check if the user redirected after email confirmation has been already set in session store. We will also provide a special layout for this case later on. During my tests the redirect and session store were quite fast so this page may be visible rather rarely.

```js
src/routes/logging-in@blank.svelte
<script>
	import { session } from '$app/stores';
	import { goto } from '$app/navigation';
	import { page } from '$app/stores';

	let redirectPath = '/dashboard';

	$: {
		const redirectTo = $page.url.searchParams.get('redirect');
		if (redirectTo) {
			redirectPath = redirectTo;
		}
		// check if user has been set in session store then redirect
		if ($session?.user?.id) {
			goto(redirectPath);
		}
	}
</script>

<section>
	<div>
		<progress class="progress" max="100" />
	</div>
	<div>
		Signing in from the email confirmation link  ...
	</div>
</section>

<style>
	.progress:indeterminate {
		animation-duration: 3.8s;
	}
</style>

```

<br />
### Dashboard Page and Route - protected users only page
In src/routes/ folder create a new file dashboard.svelte where we display some data about user stored by Supabase,

```js
src/routes/dashboard.svelte 
<script>
	export let user;
</script>

<div>
	<h3>This is protected route accesible only by logged users</h3>
	<p>Hello user {user.email}</p>
</div>
<div>
	<p>User {user.email} details:</p>
	<pre>{JSON.stringify(user, null, 2)}</pre>
</div>

```

In src/routes/ folder create a new dashboard.js file. 
```js
src/routes/dashboard.js
import { supabaseServerClient, withApiAuth } from '@supabase/auth-helpers-sveltekit';

export const GET = async ({ locals, request }) =>
	withApiAuth(
		{
			redirectTo: '/',
			user: locals.user
		},
		async () => {
			// const { data } = await supabaseServerClient(request).from('test').select('*');
			return {
				body: {
					// data,
					user: locals.user
				}
			};
		}
	);

```

<br />
### Email Confirmation Redirect Layout
In src/routes/ folder create a new file __layout-blank.svelte where we are having a special named layout for email confirmation redirect as mentioned earlier. 

```js
//src/routes/__layout-blank.svelte
<script>
	import { session } from '$app/stores';
	import { supabaseClient } from '$lib/sb';
	import { SupaAuthHelper } from '@supabase/auth-helpers-svelte';
</script>

<svelte:head>
	<title>Email and Password Demo - Supabase Auth Helpers</title>
</svelte:head>

<SupaAuthHelper {supabaseClient} {session}>
	<slot />
</SupaAuthHelper>

```
<br />
I hope this tutorial was useful. Big thank to Supabase guys for SvelteKit Auth Helpers, their examples where true source for this article. Because I am really no auth expert especialy concerning SSR all comments, corrections or enhancements are welcomed.