supabase / auth-js

An isomorphic Javascript library for Supabase Auth.
MIT License
368 stars 165 forks source link

user object warning logged, even when not touching `session.user` #888

Open j4w8n opened 7 months ago

j4w8n commented 7 months ago

Bug report

Describe the bug

With supabase-js@2.42.5 and the ssr auth helper package, after a user logs in, the below warning is logged to the server console five times, with a fairly minimal SvelteKit app. This happens despite the fact that none of my code is calling session.user, nor am I destructuring the user property from session, or destructuring with ...rest-type syntax.

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.

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

https://github.com/j4w8n/getsession-warning

Expected behavior

The warning should not log when dev code doesn't explicitly call session.user.

Root Cause

I figured out why the first three logs happen: the Supabase client's server-side storage, from +layout.ts, is returning stringified JSON. You can see this in the docs. JSON.stringify() causes each property of session to be touched during the stringify process, which inevitably calls session.user. So, each time _useSession() is called, whether implicitly through dev code like getSession() or explicitly with internal auth-js processes, the warning is being logged.

I can't figure out where the final two warning logs are coming from. Could be from the same client initialization as above, but perhaps whatever triggers it is getting run asynchronously, so it's logging later.

Even if this warning wasn't an issue in the context of ssr, it could still be problematic, as the auth-js client itself calls session.user when updating a user, setting a session, refreshing a session, and more - at least some of which could theoretically happen server-side.

System information

Additional context

https://github.com/supabase/auth-js/issues/873 https://github.com/supabase/auth-js/issues/873#issuecomment-2067684465 https://tc39.es/ecma262/multipage/structured-data.html#sec-json.stringify https://tc39.es/ecma262/multipage/structured-data.html#sec-serializejsonproperty https://github.com/supabase/auth-js/blob/master/src/GoTrueClient.ts#L1111-L1124

j4w8n commented 6 months ago

Someone having this issue when:

j4w8n commented 6 months ago

It's likely the other two warnings, that I couldn't figure out how they are triggering, are triggered when SvelteKit checks if passed values are POJOs, in certain situations. See https://github.com/supabase/auth-js/issues/873#issuecomment-2081467385

kangmingtay commented 6 months ago

hey @j4w8n, we made another attempt in this PR to further cut down on the repeated warning logs returned - basically everytime we detect that a new session is saved, we set a flag to suppress the warning internally

j4w8n commented 6 months ago

Thanks @kangmingtay. I suspect that PR will resolve issues for some people - specifically the ones exclusively experiencing this when calling things like updateUser(). However, this has basically no effect for SvelteKit users - at least not on initial login and hard refreshes - because of the nature of what I explained in the first paragraph of the Root Cause section.

As an aside, how do I test an RC? I added an override in my package.json, and after doing bun install it claimed it installed one thing. When I go to the auth-js package.json the version is 2.64.2-rc.1, but none of the code from the release is in there - I had to add it manually to test. I was looking in dist/main/GoTrueClient.js, but I even glanced at the .ts version in src and saw nothing. I had this experience with pnpm as well, in another demo app.

j4w8n commented 6 months ago

@kangmingtay looks like there is something wrong with the build process for RCs. If you checkout the code on npm, you'll see the PR code is not there. https://www.npmjs.com/package/@supabase/auth-js/v/2.64.2-rc.1?activeTab=code

kangmingtay commented 6 months ago

@j4w8n ah good point, not sure why the release workflow got skipped in the first attempt but i reran it and it's published to npm so you should be able to test it out

kangmingtay commented 6 months ago

@j4w8n yeah your analysis is right, any method that implicitly accesses the user property in the session object will trigger the warning log.

i think we can implement your suggestion here so the warning is only logged once per proxy session, but will need some time to test this out since we're currently quite tight on bandwidth

vehm commented 6 months ago

Confirming that we are continuing to receive the warning in SvelteKit after #895 as mentioned here.

Will follow.

j4w8n commented 6 months ago

Until this issue is resolved, and perhaps even after that, I've tweaked my demo app - v0.3.0 - to where I no longer receive any warnings.

My solution is done in a legitimate way, which checks all of the security boxes. The code in hooks.server.ts verifies the JWT and uses it's decoded data to craft a validated session. This session will pass any internal auth-js checks, as well as type checks.

https://github.com/j4w8n/sveltekit-supabase-ssr

silentworks commented 6 months ago

I think the suggestion here from @j4w8n is great but I'd also like a way to suppress this warning completely in production build. I don't want this to be logging inside of my application terminal when its being run in production mode.

itsmikesharescode commented 6 months ago

any update?

Salman2301 commented 6 months ago

Still logging, try different way to suppress it too, seems nothing works vite, sveltekit onwarn .... Svelte should somehow allow us to suppress certain log. Just incase if any of the lib are abusing it.

itsmikesharescode commented 6 months ago

any update??

CropWatchDevelopment commented 5 months ago

Any Updates? This is getting really annoying. I am not looking to supress the issue, I want a fix. Is there any repo that shows an example of the CORRECT way to implement supabase auth in sveltekit? It seems like new examples from Supabase are all for Nextjs

Hugo-Dz commented 5 months ago

For all folks using SvelteKit, here's a robust solution which both removes the annoying logs and avoid a network call to get user's data.

jdgamble555 commented 5 months ago

I just followed the official SvelteKit example, but without the session object. This gets rid of the log. 99% of the time, you just need the user object if there is one.

J

vehm commented 5 months ago

I just followed the official SvelteKit example...

I think part of the issue is that the docs have multiple examples with SvelteKit and they are not all updated to reflect the current "correct" methods - and, meaning no offense, comments like this just add to the confusion because it all depends on which example you followed.

...but without the session object. This gets rid of the log. 99% of the time, you just need the user object if there is one.

Can you elaborate on this?

jdgamble555 commented 5 months ago

@vehm - I'm not sure what you mean. There is only one example page that I'm aware of:

https://supabase.com/docs/guides/getting-started/tutorials/with-sveltekit

But to be clear, here is my hooks code. You usually only need user anyway and not session.

  event.locals.safeGetSession = async () => {

    const {
      data: { session },
    } = await event.locals.supabase.auth.getSession();

    if (!session) {
      return { user: null };
    }

    const {
      data: { user },
      error,
    } = await event.locals.supabase.auth.getUser();

    if (error) {
      // JWT validation has failed
      return { user: null };
    }

    return { user };
  };

That being said there is still the stupid log even when using session correctly, and needs to get fixed for people who do use session variable.

J

vehm commented 5 months ago

I'm not sure what you mean. There is only one example page that I'm aware of

Here are the three I was referring to (including the one you linked to):

Essentially, there are a few guides that all approach things a little differently and it's difficult to know which of these is the "correct" or "up-to-date" method.

jdgamble555 commented 5 months ago

They all have the same hooks file unless I'm missing something?

J

vehm commented 5 months ago

They all have the same hooks file unless I'm missing something?

The first linked guide utilizes an authGuard + sequence and the app.d.ts is also different, but yes there are currently only minor differences between them. However, even if there are minor differences now, that is not true for prior versions nor for the future.

They were most inconsistent when they made the change from getSession to safeGetSession a while back. A couple of the guides were still using getSession even though safeGetSession was the "correct" method. This is the issue I'm referring to - inconsistent updates throughout the docs for what is "correct", and the resulting confusion/frustration that could occur when one might have followed an outdated guide.

wiesson commented 5 months ago

Which one should I use so that I don't see the error? I'm also using https://github.com/supabase/auth-js/issues/888#issuecomment-2161773095 approach and thought this is the way (checking the user, then the session)

After seeing https://supabase.com/docs/guides/getting-started/tutorials/with-sveltekit example, session and user is used reversed (calling session first, then obtain the user)

The error is so annoying 🫣

scosman commented 5 months ago

Could we just get a "markUserAsVerifed" method as a interm solution if this fix is going to take much longer?

To current state of svelte+supabase is pretty frustrating. Non SSR auth has been deprecated, and SSR auth hits constant security warnings (even when doing things correctly).

j4w8n commented 5 months ago

@wiesson, there are no official examples which don't log the warning when using SvelteKit.

@scosman, yeah, bit of a mess right now, but I doubt they're going to introduce a new method before a fix happens. Maybe there will be one that comes along with the fixes?

I don't know the full roadmap of how they're addressing this, but the new ssr update is in RC right now; and asymmetric JWTs are coming, plus an augmented getUser() I believe.

However, in my opinion, more changes are needed to address the warning being logged when using things like updateUser() on the server side. I hope this part of the issue has not been overlooked. They'll either need to get rid of the warning entirely (if the above changes address the problem enough) or something else is going to have to change within code.

igorlanko commented 5 months ago

Is it okay to stay on 0.0.10 before the RC is shipped? I updated to 0.3.0, found this issue, and decided to rollback for now.

j4w8n commented 5 months ago

Is it okay to stay on 0.0.10 before the RC is shipped? I updated to 0.3.0, found this issue, and decided to rollback for now.

Since it's more of a logging nuisance than an actual issue, I'd consider staying on 0.3.0. But stick with whatever is working for you.

jstjoe commented 5 months ago

@vehm - I'm not sure what you mean. There is only one example page that I'm aware of:

https://supabase.com/docs/guides/getting-started/tutorials/with-sveltekit

But to be clear, here is my hooks code. You usually only need user anyway and not session.

  event.locals.safeGetSession = async () => {

    const {
      data: { session },
    } = await event.locals.supabase.auth.getSession();

    if (!session) {
      return { user: null };
    }

    const {
      data: { user },
      error,
    } = await event.locals.supabase.auth.getUser();

    if (error) {
      // JWT validation has failed
      return { user: null };
    }

    return { user };
  };

That being said there is still the stupid log even when using session correctly, and needs to get fixed for people who do use session variable.

J

I'm in the same boat. I implemented safeGetSession() as per the documentation but I'm getting overrun with these logs. I hope the RC people are talking about will resolve that?

thedelanyo commented 5 months ago

Still logging, try different way to suppress it too, seems nothing works vite, sveltekit onwarn .... Svelte should somehow allow us to suppress certain log. Just incase if any of the lib are abusing it.

This is really a nightmare. I cannot even console.log on server to quickly look up a behavior.

thedelanyo commented 5 months ago

@wiesson, there are no official examples which don't log the warning when using SvelteKit.

@scosman, yeah, bit of a mess right now, but I doubt they're going to introduce a new method before a fix happens. Maybe there will be one that comes along with the fixes?

I don't know the full roadmap of how they're addressing this, but the new ssr update is in RC right now; and asymmetric JWTs are coming, plus an augmented getUser() I believe.

However, in my opinion, more changes are needed to address the warning being logged when using things like updateUser() on the server side. I hope this part of the issue has not been overlooked. They'll either need to get rid of the warning entirely (if the above changes address the problem enough) or something else is going to have to change within code.

The warning is unnecessary imho, could be a warning in the docs, alongside the guides or a comment alongside the code.

The warning makes server log debugging nightmare. I can't figure out what to read anymore. I presume it even makes the server slower over time as well.

itsmikesharescode commented 5 months ago

is supabase dont give a sht with these?

itsmikesharescode commented 5 months ago

this spam msg hit my productivity already

victorforissier commented 5 months ago

For whomever that can help - while they fix this I setup a vite vite plugin that just suppresses this message.

In vite.config.ts

import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vitest/config';

// myErrorFilterPlugin.ts
import type { Plugin } from 'vite';

export function myErrorFilterPlugin(): Plugin {
    return {
        name: 'error-filter-plugin',
        configureServer(server) {
            const filterMessage =
                '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.';

            // Intercept console.warn
            const originalWarn = console.warn;
            console.warn = (...args: any[]) => {
                if (args[0] && typeof args[0] === 'string' && args[0].includes(filterMessage)) {
                    return;
                }
                originalWarn(...args);
            };

            // Intercept console.log
            const originalLog = console.log;
            console.log = (...args: any[]) => {
                if (args[0] && typeof args[0] === 'string' && args[0].includes(filterMessage)) {
                    return;
                }
                originalLog(...args);
            };
        }
    };
}

export default defineConfig({
    plugins: [sveltekit(), myErrorFilterPlugin()],
    test: {
        include: ['src/**/*.{test,spec}.{js,ts}']
    }
});
jacksteamdev commented 5 months ago

We can use #895 to suppress the warning by setting the protected property suppressGetSessionWarning on the AuthClient instance.

// @ts-expect-error - suppressGetSessionWarning is not part of the official API
event.locals.supabase.auth.suppressGetSessionWarning = true;

Here's my hooks.server.ts:

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 `/`
       * 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: '/' });
      },
    },
  });

  if ('suppressGetSessionWarning' in event.locals.supabase.auth) {
    // @ts-expect-error - suppressGetSessionWarning is not part of the official API
    event.locals.supabase.auth.suppressGetSessionWarning = true;
  } else {
    console.warn(
      'SupabaseAuthClient#suppressGetSessionWarning was removed. See https://github.com/supabase/auth-js/issues/888.',
    );
  }

  /**
   * 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';
    },
  });
};
kvetoslavnovak commented 4 months ago

In SvelteKit I am using this setup and it seems to work ok without any warning logs:

Edit: Unfortunatelly there are still some cases when the warning log is fired (e.g. internal Supabase methods such as auth.updateUser) :-(

// hooks.server.js
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'
import { createServerClient } from '@supabase/ssr'

export const handle = async ({ event, resolve }) => {
  event.locals.supabaseServerClient = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
      cookies: {
          getAll() {
              return event.cookies.getAll()
          },
          setAll(cookiesToSet) {
              cookiesToSet.forEach(({ name, value, options }) =>
                  event.cookies.set(name, value, { ...options,path: '/' })
              )
          },
      },
  })

  const getSessionAndUser = async () => {
      const { data: { session } } = await event.locals.supabaseServerClient.auth.getSession()
      if (!session) {
          return {
              session: null,
              user: null
          }
      }

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

      delete session.user
      const sessionWithUserFromUser = { ...session, user: {...user} }

      return { session: sessionWithUserFromUser, user }
  }

  const { session, user } = await getSessionAndUser()

  event.locals.session = session
  event.locals.user = user

  return resolve(event, {
      filterSerializedResponseHeaders(name) {
          return name === 'content-range' || name === 'x-supabase-api-version'
      },
  })
}
// +layout.server.js
export const load = async (event) => {
// covering the case when the user was deleted from the database, the cookie needs to be deleted in the browser
  if (event.locals.session == null) {
    event.cookies.delete(event.locals.supabaseServerClient.storageKey, { path: '/' });
  }

  return {
      session: event.locals.session,
      user: event.locals.user
  };
};
// +layout.js
import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'
import { createBrowserClient, createServerClient, isBrowser } from '@supabase/ssr'

export const load = async ({ fetch, data, depends }) => {
  depends('supabase:auth')

  const supabase = isBrowser()
    ? createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
        global: {
          fetch,
        },
      })
    : createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
        global: {
          fetch,
        },
        cookies: {
          getAll() {
            return data.cookies
          },
        },
      })

      const session = isBrowser()
      ? (await supabase.auth.getSession()).data.session 
      : data.session

  return {
    supabase,
    session,
    user: data.user
  }
}
jdgamble555 commented 2 months ago

So it turns out you do need the session variable if you're using Custom RBAC.

So, the error persists. It's been 6 months, other issues were pre-maturely closed, and this issue is not fixed!

J

juniorforlife commented 2 months ago

@Hugo-Dz @j4w8n according to this repo https://github.com/j4w8n/sveltekit-supabase-ssr/blob/main/src/hooks.server.ts

event.locals.getSession = async (): Promise<Session | null> => {
    const {
      data: { session },
    } = await event.locals.supabase.auth.getSession()

    if (!session) return null

    /**
     * Ensures the session is fully validated. See README Security section for details.
     * 
     * !!! Simply verifying the JWT does not validate the `session.user` object for use. !!!
     * See "False Security" in https://github.com/orgs/supabase/discussions/23224
     * The safest and easiest way to validate the session is by calling `getUser()`
     * and using it's returned data. An alternative, which does not make a network call, 
     * is to create a validated session; which we do below. 
     */
 ...

if getSession isn't safe then why don't we just use getUser alone? Why do they still use getSession, then check if there's a session then call getUser in the SvelteKit example in Supabase docs?

j4w8n commented 2 months ago

@juniorforlife, in my opinion, for two reasons.

  1. They call getSession first for speed and efficiency. If there's no session stored locally, then calling getUser is a waste of a network call to the Supabase backend.

  2. getUser doesn't return the actual session, it validates the user based on the JWT from getSession. So, if the point of calling event.locals.safeGetSession is to actually return the session, then you need to call auth.getSession at some point anyway.

juniorforlife commented 2 months ago

@j4w8n Thanks! From what I see in the example code, the only reason session is needed is it's used in onAuthStateChange to check expiration date. I think it's not necessary if we already check getUser in hooks.server.ts right?

j4w8n commented 2 months ago

@j4w8n Thanks! From what I see in the example code, the only reason session is needed is it's used in onAuthStateChange to check expiration date. I think it's not necessary if we already check getUser in hooks.server.ts right?

Depends on the needs of your app.

jdgamble555 commented 2 months ago

If you need to get custom claims, you will also need the session. Custom Claims are good to get user information you don't want to continuously refetch on every request (username, display name, role, etc).

Ironically, if you have to verify the session on every request via getUser, it makes this optimization redundant.

Also, there are still cases where you get the "getSession warning" even when you don't return a session at all like updateUser (and other events, we need to list them, which excessively tie to session).

The whole process is flawed and should just be handled automatically with one function. Sigh.

J

itsmikesharescode commented 2 months ago

I just blindly verify the session locally using jwt.verify just to get rid of this insane spamming satanic message.

well it turns out this way much faster since supabase giving you jwt secret key.

StevenStavrakis commented 1 month ago

Are there any in-progress fixes for this? I'd like to decide whether to use SB for upcoming projects, and I see no movement here.

j4w8n commented 1 month ago

Are there any in-progress fixes for this? I'd like to decide whether to use SB for upcoming projects, and I see no movement here.

Not that I'm aware of. You'll be fine though, since it's just a console warning; it doesn't inhibit any functionality. Mainly just annoying, especially when you're doing everything correctly.

jdgamble555 commented 1 month ago

@StevenStavrakis - I wouldn't let this deter you from using Supabase. Every software you use has tradeoffs and temporary small bugs.


It does look like the whole process is getting replaced eventually with "asymmetric keys." although there doesn't seem to be a timeline:

https://github.com/supabase/auth-js/issues/912#issuecomment-2407873025

J

j4w8n commented 1 month ago

Asymmetric JWTs are supposed to land by the end of the year.

Announcement: https://github.com/orgs/supabase/discussions/29260

Some implementation details are in here: https://github.com/orgs/supabase/discussions/29289

sean-c-johnston commented 1 month ago

Are there any in-progress fixes for this? I'd like to decide whether to use SB for upcoming projects, and I see no movement here.

Not that I'm aware of. You'll be fine though, since it's just a console warning; it doesn't inhibit any functionality. Mainly just annoying, especially when you're doing everything correctly.

I came here because this warning made it seem like I must be using Supabase incorrectly or doing something insecure with it. I'm still not 100% sure I'm safe honestly but I've followed this guide so I think I'm safe.

j4w8n commented 1 month ago

Are there any in-progress fixes for this? I'd like to decide whether to use SB for upcoming projects, and I see no movement here.

Not that I'm aware of. You'll be fine though, since it's just a console warning; it doesn't inhibit any functionality. Mainly just annoying, especially when you're doing everything correctly.

I came here because this warning made it seem like I must be using Supabase incorrectly or doing something insecure with it. I'm still not 100% sure I'm safe honestly but I've followed this guide so I think I'm safe.

You'll be good by following that SvelteKit guide. There will still be some warnings because of how SvelteKit checks some things, but no security issues there.