A Supabase auth helper for SvelteKit (in beta)
Supakit is meant to be as modular as possible. Meaning you can use the whole thing or whatever parts you'd like. There are some exceptions.
Here is the default config:
supakit: {
cookie: {
options: {
maxAge: 14400
},
route: '/api/supakit'
},
redirects: {
login: '',
logout: ''
}
}
You can override the defaults by creating a supakit.config.js
file in the root of your project.
supakit > cookie > options
takes any of the CookieSerializeOptionssupakit > cookie > route
is where ourstate
module sends a post-auth Supabase session, for setting and expiring cookies. Ourcookies
module handles setting and expiring cookies for you; but if you'd like to set a different route and/or handle cookies yourself, you can set the path here.supakit > redirects
is used for post-login/logout redirection using SvelteKit'sgoto()
.
- I've only done local tests with the GitHub OAuth login.
- You need to provide your own signIn and signOut functions.
- The
clients
module uses$env/dynamic/public
. This is to make Supakit compatible with various adapters like netlify, vercel, and static.
npm install supakit
pnpm add supakit
yarn add supakit
Enable the Supakit plugin in vite.config.js
. Note this is imported from supakit/vite
, not supakit
.
import { sveltekit } from '@sveltejs/kit/vite'
import { supakit } from 'supakit/vite'
const config = {
plugins: [sveltekit(), supakit()]
};
export default config;
Create an .env
file in the root of your project, with your PUBLIC_SUPABASE_URL
and PUBLIC_SUPABASE_ANON_KEY
values.
After setup, the following code will get you going. For more reading and options, checkout out the modules further below.
/* hooks.server.js */
import { auth } from 'supakit'
export const handle = auth
<!-- +layout.svelte -->
<script>
import { supabaseClient, state } from 'supakit'
/** @type {import('supakit/types').StateChange} */
state(null, ({ event, session }) => {
/* some post login and/or logout code */
})
/* use the supabase client */
const { data, error } = await supabaseClient.from('table').select('column')
</script>
/* server-side */
import { supabaseServerClient } from 'supakit'
/* use the supabase client */
const { data, error } = await supabaseServerClient.from('table').select('column')
Supabase user info in will be available in
locals.session.user
on the server-side.
You need to use this module if you intend to use the
state
,cookies
, orclient
modules.
Essentially, you "use" this module by importing Supakit's two Supabase clients in your code. See examples further below.
Sets up the Supabase clients and exports them. The Supabase URL and ANON KEY are pulled from SvelteKit's $env/dynamic/public
. The supabaseClient
has the autoRefreshToken
and persistSession
options set to false
.
supabaseClient
for client-side supabase work.supabaseServerClient
for server-side supabase work.initSupabaseServerClient()
, which takes in a Supabase access_token to authorize the server client. This function is automatically invoked in the Supakit serverclient
module; but available for you to use in custom code if desired.
Usage examples:
<!-- +page.svelte -->
<script>
import { supabaseClient } from 'supakit'
</script>
/* +layout.server.js */
import { supabaseServerClient } from 'supakit'
Manages a secure session store (with Svelte's context feature), and exports getSession()
. If you pass the store into the state
module, Supakit will automatically hydrate the store, post-login, with the returned Supabase session.user
info (or null
if logged out).
Usage examples:
<!-- +layout.svelte -->
<script>
import { page } from '$app/stores'
import { getSession } from 'supakit'
const { session } = getSession()
$session = $page.data.session
</script>
<!-- +page.svelte -->
<script>
import { getSession } from 'supakit'
const { session } = getSession()
</script>
{#if $session}
<h4>Your id is {$session.id}</h4>
{/if}
This module depends on the clients
module.
Handles logic for Supabase's onAuthStateChange()
. state
fetches a "cookie" route, which is configurable, when the SIGN_IN
and SIGN_OUT
events fire. It optionally takes in a writable store (typed for Supabase's User type) or null
, and a callback function which receives the Supabase event
and session
if you need to do additional work post-login/logout.
When you pass in Supakit's session store, the returned Supabase session.user
info is available in the store immediately after login and logout. This is handy if you don't want to use SvelteKit's invalidate()
or invalidateAll()
methods.
If you've configured redirects, this module will execute them with goto()
. See configuration. Keep in mind that if you set one or both of the redirect configs, the callback function won't fire whenever the events are triggered. So if you need the callback function, call goto()
yourself; as shown in the example below.
Here's a usage example. Perhaps a bit confusing, notice our store name is session
; but the callback is also receiving session
, which is Supabase's returned session post login/logout.
<script>
import { page } from '$app/stores'
import { goto } from '$app/navigation'
import { getSession, state } from 'supakit'
const { session } = getSession()
$session = $page.data.session
/** @type {import('supakit/types').StateChange} */
state(session, ({ event, session }) => {
/* some post login and/or logout code */
/* then redirect */
if (event === 'SIGNED_IN') goto('/app')
if (event === 'SIGNED_OUT') goto('/')
})
</script>
You can import and call these modules individually, in hooks.server.js
, or use our convenient auth
module to execute all three.
This module depends on the clients
module.
Sets and refreshes browser cookies. Information comes from the Supabase session
. On every server request, Supakit will attempt to refresh Supabase cookies if the jwt expires in less than 120 seconds; or has already expired. This means you should keep your cookie maxAge
at 120 seconds or longer. By default, Supakit sets maxAge
to 14400 seconds (4 hours).
Supakit will set these three cookies:
sb-user
sb-access-token
sb-refresh-token
In the process of refreshing the cookies, Supakit will update the client-side Supabase client with the new session. This keeps the client's session up-to-date for db queries, and using method's like
getSession()
andgetUser()
.
Sets the following. Note the values will always exist; it's a matter of if there's an actual value or just null
.
event.locals.session = {
user: cookies['sb-user'],
access_token: cookies['sb-access-token'],
refresh_token: cookies['sb-refresh-token']
}
This module depends on the clients
module.
Authorizes supabaseServerClient
with the sb-access-token
cookie value.
Convenienence method for calling the above three. Order is cookies, locals, client
.
Usage examples:
/* hooks.server.js */
import { auth } from 'supakit'
export const handle = auth
/* hooks.server.js */
import { sequence } from '@sveltejs/kit/hooks'
import { auth } from 'supakit'
export const handle = sequence(auth, yourHandler)
/* hooks.server.js */
import { sequence } from '@sveltejs/kit/hooks'
import { cookies, locals } from 'supakit'
export const handle = sequence(cookies, locals, yourHandler)
Sometimes you want a user to be logged in, in order to access certain pages.
Here is our example file structure, where routes /admin
and /app
should be protected. We place these routes under a layout group, so they can share a +layout.server.js
file. However, this isn't required unless you need shared data across pages under the group.
src/routes/
├ (auth)/
│ ├ admin/
│ │ ├ +page.server.js
│ │ └ +page.svelte
│ ├ app/
│ │ ├ +page.server.js
│ │ └ +page.svelte
│ ├ +layout.server.js
│ └ +layout.svelte
├ login/
│ └ +page.svelte
├ +error.svelte
├ +layout.server.js
├ +layout.svelte
└ +page.svelte
When using a +layout.server.js
file, first check for a null locals.session.user
before using a Supabase server client. You can also check locals.session.access_token
or locals.session.refresh_token
. We do this because without the presence of cookies, supabaseServerClient
is undefined. It's only initialized as a Supabase client if the sb-access-token
cookie has a non-null
value.
/* src/routes/(auth)/+layout.server.js */
import { redirect } from '@sveltejs/kit';
import { supabaseServerClient } from 'supakit';
/**
*
* @type {import('./$types').LayoutServerLoad}
*/
export const load = async ({ locals }) => {
if (!locals.session.user) throw redirect(307, '/login')
/* grab info to return */
let { data, error } = await supabaseServerClient.from('table').select('column')
return {
stuff: data.stuff
}
}
Protect pages using a +page.server.js
file for each page route. This is needed because +layout.server.js
will not necessarily run on every request. This method works for both client-side and server-side requests, because it causes handle()
to be called in hooks.server.js
for {route}/__data.json
.
To be clear, the server is called in this process; therefore we have opted out of true client-side navigation. However, this does not cause the page to be server re-rendered; as SvelteKit is only calling the server to re-run the page load()
function.
import { redirect } from '@sveltejs/kit';
/**
*
* @type {import('./$types').PageServerLoad}
*/
export const load = async ({ locals }) => {
if (!locals.session.user) throw redirect(307, '/login')
}