supabase / auth

A JWT based API for managing users and issuing JWT tokens
https://supabase.com/docs/guides/auth
MIT License
1.45k stars 351 forks source link

Supabase Service Key mode will not override RLS if a user token is applied. #965

Closed GaryAustin1 closed 1 year ago

GaryAustin1 commented 2 years ago

Bug report

In looking at a user issue with SilentWorks on Discord, a strange result was happening with SvelteKit server side requests being blocked by RLS with service_key used to init the Supabase client (in this case on server).

Seems setting a user token overrides the service_key mode of createClient.

If true this could cause strange results with left over, or applied tokens to the supabase object. This is probably the way things are supposed to work, but not sure that is well understood.

To Reproduce

I don't use server side code, so the mock up test I did is simple JS. So not a normal service_role mode, BUT I believe the results will still apply to server side code based on the network requests to PostgREST.

First Run with service key and no user signin. Table returns value. Sign in user.
Run with service key and table returns []. Turn off Signin. Still returns []. Clear local storage. Table returns value.

Difference in network requests without and with user token:

Service Key and no signed in user. Note bearer token matches service key (short) image

Service Key and signed in user. Note longer bearer token. image

Expected behavior

I acknowledge my test is not real world as browser should not run with service role, but I believe on a server with a left over user token from session or set on purpose the same result would occur based on the network request.

At a minimum it should be made clear service_key role applied to supabase client object only works with no user token applied.

Code to reproduce:

var supabase = supabase.createClient(SUPABASE_URL, SUPABASE_SERVICE_KEY)
async function app () {
/*
    const {user} = await supabase.auth.signIn({
        email:'gabbott@sommapp.com',
        password:'testpass'
    })
    */
    const { data, error} = await supabase
        .from('test')
        .select('*')
        .eq('id',55)
    console.log(data,error)
}
app()

This could be known, but 3 of us on Discord assumed service_role would always override.
Once again, I note that on the server side maybe the issue can not occur, but I suspect if the supabase object gets a user token, it overrides the service role key.

GaryAustin1 commented 2 years ago

I can also confirm that with no user signin or local storage set... Doing supabase.auth.setAuth(access_token) With a valid user token also over rides the service key role. I suspect there maybe code on server side apps that do various things with the same supabase object and if they ever set the token for one operation, other strictly service role operations will start failing.

steve-chavez commented 2 years ago

Service Key and no signed in user. Service Key and signed in user.

@GaryAustin1 Oh, wait. So you assume that both keys will have an effect on db authorization right? That's not the case actually, only the Authorization: Bearer <jwt> will affect the db.

The key in apikey is a Kong thing, it doesn't reach the db and it's only there for rejecting requests without a JWT. See this previous discussion https://github.com/supabase/supabase/discussions/1925#discussioncomment-859934.

Let me know if that clarifies things.

(Also, would have to discuss this with the infra team but it might be possible to remove the apikey Kong protection now that postgrest has an option to disallow anon requests)

GaryAustin1 commented 2 years ago

@steve-chavez I think this more a confusion thing, but it is likely users assume use of service key is absolute. As we were debugging a user issue on RLS not working with server side code involving setting token into the SB client. It came out he was using service key. All three of us assumed his code should bypass RLS. As I thought about setting token I thought it might be the issue. After seeing auth header with bearer it is clear what is happening. But if your SB client on server is doing multiple things or has left over token, (browser client pulls token from storage even if you start new client with service key). Not sure how all the server auth helpers are working. Silentworks and I thought worth reporting and at a min documenting that if your token gets set in client you lose service key from init.

kiwicopple commented 2 years ago

Perhaps this one requires a docs update. Even if we switched it to the opposite, there would still be confusion for developers, but also with the downside that their false-assumption has caused a data-breach (RLS is bypassed).

Once a user is signed in with a client, we definitely want to adhere to the RLS of that user. Let's update the docs to make this clear?

nathancahill commented 1 year ago

If you run in to this, use a bogus cookie name for the service role client so it won't grab on to the user session:

cookieOptions: {
    name: 'no-cookie-for-you'
}
TheAnimatrix commented 4 months ago
  event.locals.supabaseServer = createServerClient(PUBLIC_SUPABASE_URL, SUPABASE_KEY, {
    cookies: {
      get: () => '',
      set: (_, __, ____) => undefined,
      remove: (_, __) => undefined,
    },
  });

this is what i do

ThatGuySam commented 2 months ago

A temporary solution I use is it to sign out right after it's likely to get a session or right before I need to bypass RLS.

await supabase.auth.signOut()

Not ideal, but it's simple workaround

tdgao commented 1 month ago

If anyone is encountering the issue now and using Vercel serverless functions, try to purge the cache in Vercel. It should get rid of what ever cookie that was there overriding the service key.

Also, I added in auth properties to disable it for serverside.

const supabase = createClient(PUBLIC_SUPABASE_URL || "", SUPABASE_SERVICE_KEY || "", {
    auth: {
      autoRefreshToken: false,
      persistSession: false,
      detectSessionInUrl: false
    }
});
ianscott313 commented 1 month ago

I also use Vercel serverless functions. I had trouble with this as well and then I realized it was because I was calling supabase.auth.signUp with Confirm email disabled.

When Confirm email is disabled, supabase.auth.signUp returns a user and session. When its enabled, only the user is returned without the session.

The user session was getting cached on my backend essentially overriding the service role and making all the other service role jobs fail. I was able to resolve this by just signing out the user after signing them up.

Forewarning - This issue is compounded if you delete the user before signing them out because supabase.auth.signOut fails when a user does not exist in the auth table even though the session stays cached on server. If that happens, you'll have to somehow clear the cookies/session management your backend uses.

ssadel commented 3 weeks ago

Setting global auth header with the service key seems to bypass any user session stuff instead of signing out users before making service requests

const client = createClient<Database>(
    url, serviceKey, 
    { 
        auth: {
            autoRefreshToken: false,
            persistSession: false,
            detectSessionInUrl: false
        },
        global: {
            headers: {
                Authorization: `Bearer ${serviceKey}`
            }
        }
    }
);