supabase / auth-js

An isomorphic Javascript library for Supabase Auth.
MIT License
354 stars 159 forks source link

supabase.auth.setSession doesn't set and persist session for the current client #474

Closed beppek closed 1 year ago

beppek commented 1 year ago

Bug report

Describe the bug

I'm having some issues with supabase.auth.setSession in 2.0.0-rc.10 where calling that method seems to set the session, it returns the user and a new refresh_token. But calling supabase.auth.getUser or supabase.auth.getSession after that returns null for user and session respectively.

Any attempts to update rows with RLS tied to the user's id after that fail.

Maybe worth noting that I'm using this in a server context.

To Reproduce

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

  1. In a server-side request use the access_token or refresh_token to set the session (using supabase.auth.setSession)
  2. Follow that up by trying to get the user and session (supabase.auth.getUser and supabase.auth.getSession)
  3. The resulting data will be null
  4. Try to update row with RLS tied to user.id
  5. It will fail with a 404 code

Expected behavior

The method should set the session for the supabase client and future requests to Supabase.

System information

Additional context

I'm running this in a Remix.run project on Cloudflare Pages (only local so far using wrangler to emulate the worker environment).

Aerilym commented 1 year ago

This sounds like the same issue I was having, it seems like the supabase.auth.getSession is broken for tokens. I've got my fix here to use refresh tokens (using supabase.auth.setSessionFromRefreshToken) but the final fix will probably need to be done in setSession. #473

beppek commented 1 year ago

I'll give that a try tomorrow, I temporarily downgraded to 1.35.7 (it's a fresh project so the refactor was brief).

j4w8n commented 1 year ago

@beppek did you resolve this? If so, can you close this issue?

Using setSession, getSession and getUser on the server-side requires configuring the supabase client with option { auth: { persistSession: false } }.

Alternatively, for getUser, pass in a jwt.

We need better docs for this.

edgarsilva commented 1 year ago

@beppek did you resolve this? If so, can you close this issue?

Using setSession, getSession and getUser on the server-side requires configuring the supabase client with option { auth: { persistSession: false } }.

Alternatively, for getUser, pass in a jwt.

We need better docs for this.

This was very difficult to find, having it in the docs would be great, would've saved me a few hours, @j4w8n suggestion solves the issue for me on the server side, without { auth: { persistSession: false } } this won't work, setSession() gives you back the correct session/user response data, but getSession() is always empty, some example code.

This fails with out the recomended fix:

// Supabase Client
// Supabase
import { createClient } from '@supabase/supabase-js';
import { env } from '@/env/client.mjs';
import { type Database } from './schema.types';

export const anonClient = createClient<Database>(
  env.NEXT_PUBLIC_SUPABASE_URL,
  env.NEXT_PUBLIC_SUPABASE_ANON_KEY
);

function handler() {
  // some-handler.js
  const sessionRes = await anonClient.auth.setSession({
    access_token,
    refresh_token,
  });
  console.log('setSession.sessionRes', sessionRes); // This response has the correct session data

  const userSession = await anonClient.auth.getSession();
  console.log('userSession 1:', userSession); // This is empty
   // userSession 1: { data: { session: null }, error: null }

  profile = await anonSupabaseClient
    .from('profiles')
    .select('*')
    .eq('user_id', current_user_id)
    .limit(1);

  console.log('user Profile:', profile); // Also empty since RLS is still validating against Anon Session
  //....  more code
}

With @j4w8n suggestion works as expected, an fixes the issue, I think we should have a way to remove the session from the client, right now is only private.

This works with the fix:

// Supabase Client
// Supabase
import { createClient } from '@supabase/supabase-js';
import { env } from '@/env/client.mjs';
import { type Database } from './schema.types';

export const anonClient = createClient<Database>(
  env.NEXT_PUBLIC_SUPABASE_URL,
  env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
  { auth: { persistSession: false } },
);

function handler() {
  // some-handler.js
  const sessionRes = await anonClient.auth.setSession({
    access_token,
    refresh_token,
  });
  console.log('setSession.sessionRes', sessionRes); // This response has the correct session data

  const userSession = await anonClient.auth.getSession();
  console.log('userSession 1:', userSession); // This now has the user session as expected
  // {
  //   data: {
  //     session: {
  //       access_token: 'some_token',
  //       refresh_token: 'refresh_token',
  //       user: [Object],
  //       token_type: 'bearer',
  //       expires_in: 891.1500000953674,
  //       expires_at: 1671133679
  //     }
  //   },
  //   error: null
  // }

  profile = await anonSupabaseClient
    .from('profiles')
    .select('*')
    .eq('user_id', current_user_id)
    .limit(1);

  console.log('user Profile:', profile);
  // This now logs the correct profile record, since RLS is now validating 
  // against the correct user Session
  //....  more code
}

It would be great to have auth._removeSession() made public so we can remove the session from the client without signingOut the user in other places, @j4w8n @Aerilym what do you think? or is there any other reason why removing a session from the client is not allowed?

j4w8n commented 1 year ago

@edgarsilva, yep, you're correct about calling setSession() on the server-side - without setting persistSession to false.

Behind the scenes, it returns the session, but it never saves it for the client; therefore, a call to getSession() fails to return the expected data.

What use-case are you running into for your request? I assumed calling signOut() would only take place for the current client. At least that's how it works across browsers during my testing.

edgarsilva commented 1 year ago

@j4w8n you are completely right 🤔 ... I was under the impression that it was signing me out from my other server, but I might've been doing something incorrectly, calling supabaseClient.auth.signOut() only signs out the current client, which is perfect for my two server setup case, Thanks!

BTW @beppek, on a side note I found out you can create the client and set the session for another user with just the access_token in one go like this:

NOTE: I was never able to make this one work, the above one works fine

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
  {
    global: {
      headers: {
        Authorization: `Bearer ${supabaseAccessToken}`,
      },
    },
  }
)

The docs for this are in the next-auth docs here -> Next-auth Supabasse Adapter, and one of the Supabase team members implements it like that for this example here in youtube

beppek commented 1 year ago

Hey, thanks for checking in on this. I downgraded to 1.x whatever version I was on at the time and haven't had time to upgrade and try this yet. I'll try to get back to it next week and if it works as expected I'll close this issue.

kangmingtay commented 1 year ago

Hey everyone, seems like this issue has been resolved with the recent gotrue-js releases. We've also updated our docs awhile back to highlight that creating the supabase client on the server-side would require one to set the persistSession option to false: https://supabase.com/docs/reference/javascript/auth-api