DEV Community

Cover image for Supabase SSR Auth with SvelteKit

Supabase SSR Auth with SvelteKit

kvetoslavnovak on December 01, 2023

EDIT: This tutorial was originaly released for @supabase/supabase-auth-helpers package and later rewritten for @supabase/ssr package beta version. ...
Collapse
 
maxkozlowski profile image
Maksymilian Kozlowski

Awesome article, thank you!

Have you tried doing OAuth? Any chance you could add this to the article too?

Collapse
 
kvetoslavnovak profile image
kvetoslavnovak

Thank you for your interest.
I definitely plan to look into OAuth as well.

Collapse
 
uw profile image
uwork

Something I noticed when duplicating a logged-in session on 2 browser tabs. If I log out on the second tab and click around on the first tab, the behavior is 'interesting'.

  • I observed the cookie is cleared so the user session is gone as expected in both tabs
  • On the first tab: If I refresh the page from the profile page or one of its sub-pages, it redirects back to the home page and updates the nav bar properly as expected.
  • On the first tab: if I am on the profile page and click a sub-page (eg: change password) the body of the page is updated (redirected to home) but the nav bar is not updated and still shows the 'Logout' button. Any idea what's causing this difference in behavior?
Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
uw profile image
uwork

I am still seeing the same behavior on the browser (logout button is still visible). I added a console.log on onMount and noticed it doesn't get printed when following this sequence of actions.

Not sure if related, I am using TS, and the IDE warns me on the line onMount(async () => {
that:
Svelte: Argument of type  () => Promise<() => void>  is not assignable to parameter of type  () => (() => any) | Promise<never> 

Thread Thread
 
Sloan, the sloth mascot
Comment deleted
 
Sloan, the sloth mascot
Comment deleted
 
uw profile image
uwork

Thank you for your efforts. I'll take a look!

Thread Thread
 
Sloan, the sloth mascot
Comment deleted
 
uw profile image
uwork • Edited

Hey, this video explains this problem well and suggests a solution:
Hope it helps anyone else as well!

To reload the layout view after clicking on a navigation item do this:

<a href="/sub-page" class:text-muted-foreground={$page.url.pathname !== '/sub-page'}>your sub page</a>
Enter fullscreen mode Exit fullscreen mode

This appends a CSS class if the URL does not match the path name.

Collapse
 
kvetoslavnovak profile image
kvetoslavnovak

I have updated the tutorial. Now everything works correctly and in sync. The point was to properly implement invalidation listeninng to supabase.auth.onAuthStateChange.

Please test the app and let me know if it works for you as well.

Collapse
 
cropwatchdevelopment profile image
CropWatch

Let me first say, thank you so much for this awesome article!
I am however wondering about the warning I keep getting:

Using the user object as returned from supabase.auth.getSession() or from some supabase.auth.onAuthStateChange() events could be insecure! This value comes directly from the storage medium (usually cookies on the server) and many not be authentic. Use supabase.auth.getUser() instead which authenticates the data by contacting the Supabase Auth server.

Any ideas how to remove this? All of my attempts have fallen flat.

Also, an OAuth article would be awesome!!!

Thank you!!!

Collapse
 
kvetoslavnovak profile image
kvetoslavnovak • Edited

Hi, thank you for your thank you :)

Concerning the warning I recommend to follow Supabase recently updated official documentation. I did not have time to update this tutorial. But I guess you should be good as long as you call supabase.auth methods with supabase.auth.getUser() to check JWT validation.

Try this event.locals.safeGetSession, see the new documentation here

import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'
import { createServerClient } from '@supabase/ssr'
import type { Handle } from '@sveltejs/kit'

export const handle: Handle = async ({ event, resolve }) => {
  event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
    cookies: {
      get: (key) => event.cookies.get(key),
      /**
       * Note: You have to add the `path` variable to the
       * set and remove method due to sveltekit's cookie API
       * requiring this to be set, setting the path to an empty string
       * will replicate previous/standard behaviour (https://kit.svelte.dev/docs/types#public-types-cookies)
       */
      set: (key, value, options) => {
        event.cookies.set(key, value, { ...options, path: '/' })
      },
      remove: (key, options) => {
        event.cookies.delete(key, { ...options, path: '/' })
      },
    },
  })

  /**
   * Unlike `supabase.auth.getSession()`, which returns the session _without_
   * validating the JWT, this function also calls `getUser()` to validate the
   * JWT before returning the session.
   */
  event.locals.safeGetSession = async () => {
    const {
      data: { session },
    } = await event.locals.supabase.auth.getSession()
    if (!session) {
      return { session: null, user: null }
    }

    const {
      data: { user },
      error,
    } = await event.locals.supabase.auth.getUser()
    if (error) {
      // JWT validation has failed
      return { session: null, user: null }
    }

    return { session, user }
  }

  return resolve(event, {
    filterSerializedResponseHeaders(name) {
      return name === 'content-range' || name === 'x-supabase-api-version'
    },
  })
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
cropwatchdevelopment profile image
CropWatch

I Understand now and based on your example I was able to solve the issue. Again, THANK YOU!

Thread Thread
 
kvetoslavnovak profile image
kvetoslavnovak

I have just updated the tuutorial to refelect new ssr package changes. My code should also solve the warning issues.

Collapse
 
yournewempire profile image
Archie Smyth • Edited

Great article, you are much more talented and focused than I am. I love the way you avoid the "infamous warnings" , very elegant but that's me. My only question is can getUser return null but also no error ? One q. Should I check for !user also? Wonder if you know anything about this?

Collapse
 
kvetoslavnovak profile image
kvetoslavnovak • Edited

Thank you very much. I still remember to be a total noob to programming. In my case it is like other skills, it takes time, focus and practice. But till today I do not consider myself to be talented.

Collapse
 
rx40 profile image
Petrus-Nauyoma

Hi mate.
Do you have a public GitHub repo?
Want to start a SaaS project from it.
This will support multi users having their own separate sessions once deployed right?
Tried to do the session validation in the middleware on dotnet with Supabase but all my users ended up sharing the same session.
So I don't want to have to rethink all that and just get started with protected routes from your Svelte+supabase starter

Collapse
 
kvetoslavnovak profile image
kvetoslavnovak • Edited

Hi, if you need session validation I recommend you this github.com/j4w8n/sveltekit-supabas... repo. For validation look into hooks.server.ts github.com/j4w8n/sveltekit-supabas...
file.
Supabase guys are working on introducing asymmetric JWTs so expect changes anyway.

Collapse
 
kennek profile image
Kennedy

Thanks again for the article. I am still not all the way there though. Everything works as expected in development but when I try to deploy on vercel, I get the error:

RollupError: "PUBLIC_SUPABASE_URL" is not exported by "virtual:$env/static/public", imported by "src/routes/+layout.ts".

Collapse
 
kvetoslavnovak profile image
kvetoslavnovak

Hi, thank you for yout thank you.
Concernig Vercel did you set up all your environment varaibles in Vercel? vercel.com/docs/projects/environme...

Collapse
 
uw profile image
uwork

Awesome article, you have helped me so much as I am new to the JS world. Please do an OAuth one with Supabase+SK as well! Thanks!!

Collapse
 
kvetoslavnovak profile image
kvetoslavnovak

Glad to hear you did like the tutorial.

Collapse
 
jandriw profile image
Alejandro

Thank you for this post kvetoslanovak!

Works perfectly! 😁😍

Collapse
 
dylanb101 profile image
dylanb-101

just started a new project and decide to use these two; great article!

but I was wondering what the addUserprofileToUser function did?

Collapse
 
kvetoslavnovak profile image
kvetoslavnovak

Thank you, I have added the explanaton as a second edit in the bottom of the article.

Collapse
 
asahelk profile image
Asahel

Great article! I'm new with sveltekit, Is there a form to avoid duplicate load function for session validation?

Collapse
 
kvetoslavnovak profile image
kvetoslavnovak

Hi, thank you.
I am not sure which duplicate load function do you mean? Could you be more specific, please?

Collapse
 
asahelk profile image
Asahel • Edited
export async function load({locals: { getSession }}) {
    const session = await getSession();
    // if the user is already logged in return him to the home page
    if (session) {
        redirect(303, '/');
    }
  }
Enter fullscreen mode Exit fullscreen mode

Each +page.server.js is checking if user is already looged in. Is there a way to check it in the +layout.server.js?

Thread Thread
 
kvetoslavnovak profile image
kvetoslavnovak • Edited

I would not recommend using +layout.server.js for auth checking. See more details in this discussion
github.com/sveltejs/kit/issues/6315 or this video https://www.youtube.com/watch?v=UbhhJWV3bmI&ab_channel=Huntabyte

Conrening protected routes and redirects in one place you may follow this advice https://www.youtube.com/watch?v=K1Tya6ovVOI&ab_channel=Huntabyte and move the logic to src/hooks.server.js

Collapse
 
kennek profile image
Kennedy

Thanks for the tutorial. What would the app.d.ts file look like for those of us in love with typescript?

Collapse
 
kvetoslavnovak profile image
kvetoslavnovak • Edited

I am just discussing the update of the official documentaton with Supanase. This documentaton uses TS and can be found here: supabase.com/docs/guides/auth/serv...

In previous documentation for Supabase Auth Helpers there is src/app.d.ts supabase.com/docs/guides/auth/auth...

// src/app.d.ts

import { SupabaseClient, Session } from '@supabase/supabase-js'
import { Database } from './DatabaseDefinitions'

declare global {
  namespace App {
    interface Locals {
      supabase: SupabaseClient<Database>
      getSession(): Promise<Session | null>
    }
    interface PageData {
      session: Session | null
    }
    // interface Error {}
    // interface Platform {}
  }
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
jacouys profile image
Jaco

Thanks for the great article!
Pls share the ./utils/addUserprofileToUser.js file

Collapse
 
kvetoslavnovak profile image
kvetoslavnovak • Edited

Thank you, I am sharing this function in a second edit in the bottom of the article.