supabase: Auth session is not persistent

Version

@nuxtjs/supabase: ^1.0.2 nuxt: ^3.7.1

Reproduction Link

https://github.com/bart/test-supabase

Steps to reproduce

  1. Add SUPABASE_URL and SUPABASE_KEY to your .env file
  2. npm run dev
  3. localhost:3000 should redirect you to the login page. Enter username and password and click Login button
  4. You should be redirected to /
  5. Give it a refresh and you are logged out (redirected back to /login) again

What is Expected?

Auth session should be persistent. Auth guard should not redirect to login page as long session isn’t expired or user does not sign out manually.

What is actually happening?

Auth guard redirects back to login page assuming user isn’t logged in.

About this issue

  • Original URL
  • State: open
  • Created 10 months ago
  • Reactions: 7
  • Comments: 35 (2 by maintainers)

Most upvoted comments

Seems like Safari is not accepting secure cookies on localhost. So for users that want to use Safari for local development, you can fix this bug by setting cookieOptions.secure to false in your nuxt.config.ts file:

export default defineNuxtConfig({
  supabase: {
    cookieOptions: {
      secure: process.env.NODE_ENV === 'production',
    }
})

Posting here as well since it seems related, I just put up PR https://github.com/nuxt-modules/supabase/pull/272 to address the invalid claim: missing sub claim I was getting that seemed to be caused by a race condition in the useSupabaseUser composable when making an API call right after login that tried to read the user cookie to get some user data on an internal API route.

By changing the useSupabaseUser composable to an async function and properly handling the promise called within it, I eliminated the errors I was seeing on my end.

I updated my example “confirmation” code from the example to this:

const user = await useSupabaseUser();
watch(
  user,
  async () => {
    if (user.value) {
      const { data } = await useFetch("/api/user", useCookieHeader());
      console.log(data);
    }
  },
  { immediate: true }
);

I properly received the data from the endpoint I no longer had these issues


Hopefully this helps anyone else currently stuck on this

I can confirm that this happens since v1.0.0

@larbish is there any timeline on this issue? Unfortunately the package as it is now cant really function as an effective auth solution

I think the reason for that redirect is an empty user object (null) loaded in the middleware on server-side page loads (for example on a hard refresh). You can verify it by using a custom middleware and logging useSupabaseUser() On client side it’s value is set but on server-side it is null.

A work around would be a custom middleware that only gets executed on client side but in my opinion that’s not a rock solid solution:

// /middleware/auth.js
export default defineNuxtRouteMiddleware((to, _from) => {
    const user = useSupabaseUser()
    if (!user.value && process.client) {
        return navigateTo('/login')
    }
})

If I also add a custom server-side auth middleware (in /server/middleware folder) that tries to load await serverSupabaseUser(event) I’m getting the same error as described in https://github.com/nuxt-modules/supabase/issues/238

I think we need a solution for this issue as soon as possible. Otherwise this Nuxt module doesn’t make any sense - at least for authentication.

My composable was very specific to my needs. Here is a stripped down version…honestly not sure if this follows best practice or not, but it’s working well in production:

import { createClient } from '@supabase/supabase-js'

const supabase = createClient("url", "key");

const logged_in = ref(false);
const user_id = ref();
const user_meta = reactive({
    first_name: '',
    last_name: '',
    state: ''
});

export const useUserMeta = () => {

    const getUserMeta = async () => {
        if (logged_in.value == true) {
            return;
        }
        try {
            const { data, error } = await supabase.auth.getSession();
            Object.assign(user_meta, data.session.user.user_metadata);
            logged_in.value = true;
            user_id.value = data.session.user.id;
        } catch (error) {
            console.log("ERROR fetching user:", error);
        }
    }

    const saveUserMeta = async () => {
        try {
            const { data, error } = await supabase.auth.updateUser({
                data: user_meta
            });

            if (error) throw error;

        } catch (error) {
            console.log("ERROR saving user:", error);
        } finally {
            console.log('USER UPDATED');
        }
    }

    const handleLogin = async (email) => {
        try {
            const { error } = await supabase.auth.signInWithOtp({
                email: email,
                options: {
                    emailRedirectTo: 'http://example.com/',
                }
            })
            if (error) throw error
            else return true
        } catch (error) {
            console.log(error.error_description || error.message);
        }
    }
  
    return {
        user_id,
        user_meta,
        getUserMeta,
        saveUserMeta,
        handleLogin
    };
}```

@nonInfelix , it’s just good practice. Working with environment variables (and .env files) allows you to change settings between environments (production, development, …) without having to change several parts of your code