GithubHelp home page GithubHelp logo

Comments (19)

j4w8n avatar j4w8n commented on September 26, 2024 2

If this is the case, then I'd swap "perfectly fine" with "unneeded" and use the anon key. I'd say best practice is to only use the service role key when needed.

I checked. Turns out that even if you use the service_role API-key, it will NOT bypass RLS, and instead use just the header key for RLS with role="authenticated" or "anon", ignoring service key. So that means it's perfectly fine to use the service_role API-key server-side in this way.

Yeah, this is expected; as the role is important on the backend. As you've discovered, you can create your own service role JWTs.

Remarkably, you can also use the anon-key as the API-key, but if the header-key has role="service_role", then even with the anon key, you will bypass RLS. You don't actually need the service_role API-key to bypass RLS.

Agreed.

Of course, you wouldn't want to reveal your service key publicly. Instead, for example, in the browser you'd pass along the anon-key. The JWT secret is the key that unlocks everything, it seems.

Looks like you've got a solution! One thing to keep your eye on is this PR. I forgot about it, until someone randomly approved it this morning. I'm not sure if/when it'll drop.

from supabase.

j4w8n avatar j4w8n commented on September 26, 2024 1

I'm not familiar with the Python implementation of Supabase. It looks like create_client has a headers option though, so you'd set the Authorization header to Bearer <jwt-value>. This is enough to make Supabase calls as the user. However, things like get_session(), get_user(), and refresh_session() will not work because the Supabase client is not aware of any session - that is, if the Python implementation works similarly to the Javascript one.

This initializing page has a "With timeout option" tab, in case you're not familiar with how to attach the headers option to the client.

from supabase.

j4w8n avatar j4w8n commented on September 26, 2024 1

Awesome!

I don't believe you'd want to use the service role key in these situations, as it bypasses RLS - only use the anon key.

from supabase.

Hallidayo avatar Hallidayo commented on September 26, 2024 1

Hi @swamidass - Just checking in here, did you get this all working?

from supabase.

swamidass avatar swamidass commented on September 26, 2024

Note that the same problem arises with the anon key.

from supabase.

J0 avatar J0 commented on September 26, 2024

Hey @swamidass,

Thanks for the query - can I quickly confirm that you're looking to modify the API Key and not the JWT Token used for Authorization? The example above seems like it might be editing the API Key rather than the token. While both are currently structured as JWTs they have distinct roles and the latter is primarily used for authentication against the Auth API.

The invalid API error returned might occur as the API Key is validated at the API gateway level which might not allow for changes. We will soon make the distinction between the key and token clearer as we are considering a move away from use of JWT as an API Key.

If you're looking to modify the JWT Token instead one option is to use an Auth Hook

Hope this helps and let us know if there are further queries
Joel

from supabase.

swamidass avatar swamidass commented on September 26, 2024

Thanks for help. I do not really understand the difference between "API Key" and "JWT Token used for Authorization". As I understand it, the supabase client uses an api key that is a jwt token to authorize?

But I suppose you are saying that at some point, when using a key with create_client, the system does an exact character for character lookup of the key somehow. Is that correct? If so, that isn't documented any where I could find (with documentation that seems to say something quite different), and might explain the issue. But it still leaves me with some questions on how to solve my use case.

What We Are Trying to Do

Right now our user authentication is handled by cognito, not supabase, and we can authenticate users through the OATH2 flow at our website, without touching supabase. Once we authenticate them, I want to grant each user a key to use that encodes a few details (e.g. their email), which we can then use for RLS as they make requests to supabase.

No users are currently included in the supabase auth users table, and it seems like it would be really challenging to change this. Maybe their could be a migration path to this down the line, but I'm not sure. We could include a single "webui" user (or something like that), as long as we can dynamically add different claims each time. In that case, I'm not sure a hook will work.

The Key Questions

So what approach should we take to this? Perhaps these are the key questions:

Outside of supabase, how can I dynamically add different claims to the same supabase user in a way that supabase can see these claims and use them for RLS?

Or is their a better way to architect this?

from supabase.

j4w8n avatar j4w8n commented on September 26, 2024

Thanks for the explanation @swamidass. You can definitely use custom JWTs for this.

I think the challenge would be deciding when to set the JWT to expire. If you set it too short, it might stop working while the user is still logged-in. If too long, then if the JWT is compromised, it could be used to access that user's data. And in this scenario, there's no way to refresh the JWT with a refresh token. I suppose you could create a just-in-time JWT for each Supabase request that a logged-in user had; and set the expiration for like two minutes in the future. You'd need to do this on the server-side, since you'd be using your project's JWT secret to sign the JWTs.

Then, you could setup an RLS policy that checks whatever unique claims you put in the JWT (e.g. email).

from supabase.

swamidass avatar swamidass commented on September 26, 2024

I can manage the refresh through the UI backend. That isn't hard in the use case I'm envisioning.

I'm still unclear on how to actually create a key that will work with API, and how to actually attached it to the supabase client. Can you point me to any example snippets or documentation?

from supabase.

swamidass avatar swamidass commented on September 26, 2024

Okay, it seems that I now have some working code. In python (or nodejs), first make the key.

my_key =  jwt.encode(claims, secret, algorithm="HS256")

Then make a supabase client that has this key in the auth header, but uses either the anon or service_role key,

supabase = create_client(
    url,
    service_key,
    ClientOptions(headers={"Authorization": f"Bearer {my_key}"}),
)

Now postgres see the claims just fine. These claims can be really anything you like. Moreover,

  1. Supabase will respect the "role" claim, which you can set to authenticated, service_role, anon, or anything else.
  2. Supabase will respect the "exp" claim, which must be in the future and you can either leave out for a non-expiring key.
  3. Supabase will respect the "iat" claim, which must be in the past, or you can leave it out.

This works painlessly. I just wish it was documented somewhere!!!

from supabase.

swamidass avatar swamidass commented on September 26, 2024

I checked. Turns out that even if you use the service_role API-key, it will NOT bypass RLS, and instead use just the header key for RLS with role="authenticated" or "anon", ignoring service key. So that means it's perfectly fine to use the service_role API-key server-side in this way.

Remarkably, you can also use the anon-key as the API-key, but if the header-key has role="service_role", then even with the anon key, you will bypass RLS. You don't actually need the service_role API-key to bypass RLS.

Of course, you wouldn't want to reveal your service key publicly. Instead, for example, in the browser you'd pass along the anon-key. The JWT secret is the key that unlocks everything, it seems.

from supabase.

J0 avatar J0 commented on September 26, 2024

Thanks for the explanation!

Thanks for help. I do not really understand the difference between "API Key" and "JWT Token used for Authorization". As I understand it, the supabase client uses an api key that is a jwt token to authorize?

We use a an API Key, which is a JWT Token in addition to a separate JWT Token contained in the Authorization header (e.g. Bearer <token>) for authorization.

But I suppose you are saying that at some point, when using a key with create_client, the system does an exact character for character lookup of the key somehow. Is that correct?

I don't think it's an exact character match. It does a verification of the claims after verifying the token using the JWT Secret.

We could include a single "webui" user (or something like that), as long as we can dynamically add different claims each time. In that case, I'm not sure a hook will work.

If you can encode the logic for dynamic generation in JavaScript / SQL you should be able to add the claims in the Hook.

If this is the case, then I'd swap "perfectly fine" with "unneeded" and use the anon key. I'd say best practice is to only use the service role key when needed.
I would second @j4w8n 's recommendation to use the service role key when needed as a leak of the service role key would allow someone admin access to your project.

This works painlessly. I just wish it was documented somewhere!!!

Congrats on getting this working - I'll take this info back to the team. We do allow for this in Edge Functions though I'm not sure if overriding the Authorization is something we'll actively support in the long term.

Thanks @j4w8n for jumping in and @swamidass feel free to let us know if there are any other questions or concerns.

Thanks!

from supabase.

swamidass avatar swamidass commented on September 26, 2024

So it seems that I have an additional problem, this time in nodejs. I need to be able to use multiple different access keys in a given process. That creates a problem, becase:

  const client1 = createClient<Database>(url, public_key, {
    global: { headers: {Authorization: `Bearer ${key1}`}}
    auth: {
      autoRefreshToken: false,
      persistSession: true,
      detectSessionInUrl: true,
    },
  });
  const client2 = createClient<Database>(url, public_key, {
    global: { headers: {Authorization: `Bearer ${key2}`}}
    auth: {
      autoRefreshToken: false,
      persistSession: true,
      detectSessionInUrl: true,
    },
  });

Produces this warning:

Multiple GoTrueClient instances detected in the same browser context. It is not an error, but this should be avoided as it may produce undefined behavior when used concurrently under the same storage key.

I don't get this error in python.

What is the correct way to specify the access key? Is there a flag I can pass to ensure that gotrue isn't getting initialized? Or a way to alter the headers on a singleton supabase client so that each call can have a different access key?

So how are we suppoed

from supabase.

j4w8n avatar j4w8n commented on September 26, 2024

I'm not sure why you're getting that warning in node - unless you're using something like jsdom, that creates document.

Nonetheless, you should be fine with what you're doing as long as you're not logging in any users with these clients - implying you don't have to worry about the clients fighting over the same storage object, as the log warns about.

As an aside: if this is truly being done on the server side, you can change detectSessionInUrl to false as well.

EDIT: Oh wait. You're persisting the session as well. This can be false too - unless I'm missing something.

from supabase.

swamidass avatar swamidass commented on September 26, 2024

Oh, dang. I should have set that to false. I'll let you know if that didn't work.

from supabase.

swamidass avatar swamidass commented on September 26, 2024

Alright, so with this code:

  createClient<Database>(url, public_key, {
    auth: {
      autoRefreshToken: false,
      persistSession: false,
      detectSessionInUrl: false,
    },
  });
  createClient<Database>(url, public_key, {
    auth: {
      autoRefreshToken: false,
      persistSession: false,
      detectSessionInUrl: false,
    },
  });

I still get the warning. And to clarify, this is in the browser.

from supabase.

j4w8n avatar j4w8n commented on September 26, 2024

Gotcha. If you're in the browser, then yeah, that warning will throw if you create multiple browser clients. Even with that warning, what you're doing shouldn't be an issue, since you're not storing any sessions in storage (via traditional logins or a call to setSession()) - or at least I assume you're not, since you were manually setting the authorization header before.

from supabase.

jzhou891 avatar jzhou891 commented on September 26, 2024

@Hallidayo Hey just an FYI I tried doing this in python and the minted token fails to pass RLS with the anon key but works with the service key. I looked into the code, and the reason why is because the session doesn't get initialized, which means the _get_token_header(self) function in the client.py file for SyncClient falls back to the supabase_key. (in this case the anon key) that is provided during client initializing. From skimming the code it looked like the session never got set based on the headers that got passed in.

I was able to get it to work by simply authing the created client with the minted token using postgrest.auth but I still feel like this logic should be baked into the constructor. Would be open to making a PR in that case.

from supabase.

Hallidayo avatar Hallidayo commented on September 26, 2024

Hi @jzhou891 - thank you for the comment, yeah 100% feel free to open a PR πŸ‘

from supabase.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.