How to use RLS when using better-auth
Hey everyone! I have a next.js app and I use supabase + better-auth for authentication.
I would like to use the supabase browser client and for that I will need to setup policies (RLS) but I'm not sure how can I get the user id in the database queries. the auth.uid() works only when using supabase auth feature.
I already tried to pass custom JWT in the headers and created test RPC function that returns the user id from the jwt but it doesn't recognize it. is there a way to do it?
45 Replies
The sub claim in the JWT is what is pulled from auth.uid(). As long as the JWT is valid for Supabase things like role and claim are passed on to the auth functions and RLS.
https://supabase.com/docs/guides/auth/jwts
https://supabase.com/docs/guides/auth/jwt-fields
@garyaustin just like you said it worked with the default database functions to get user id / role when used similar format that it expects!
However better auth user id format is different from supabase and it gives me this error
If I hardcode dummy user id it works fine
is there a way to get stuff from it without the default functions of supabase?
my only goal is to make RLS usable with better auth
If your sub is not a UUID type from you auth provider then you can't use auth.uid(). You would need to decode your own claim with auth.jwt()->>'sub' or auth.jwt()->>'id'.
I'm also worried that I need to match the format of supabase in the jwtPayload
auth.jwt will get any claim in a valid JWT signed by the supabase secret.
You must provide your JWT in the Authorization header.
If your sub is not a UUID type from you auth provider then you can't use auth.uid(). You would need to decode your own claim with auth.jwt()->>'sub' or auth.jwt()->>'id'.I will need to validate the jwt on my own? like creating similar function like the default one of supabase?
You must provide your JWT in the Authorization header.It should be in the header ofc
No you need to sign it with the JWT secret. Then Supabase will validate it.
I configured it with
createClient<Database>(supabaseUrl, supabaseKey, {
accessToken: getAccessToken
});
Yes. That function sets the authorization header.
So you have to provide minimum claims plus your own and then sign it with the Supabase JWT secret.
Is there a way to manually validate the jwt payload?
on the supabase side, as db function
Not on the Supabase server side.
It is automatic with signing with the JWT secret.
So you have to provide minimum claims plus your own and then sign it with the Supabase JWT secret.that's what I did, but supabase refuse to claim it because of the format of the user-id of better-auth
Yes.
auth.uid() won't work if sub is not a UUID.
So either don't use auth.uid() or put your claim in a different claim.
I don't remember if sub as a UUID is required to get into postgREST but I don't think so. Just auth.uid() won't work.
Not using auth.uid() sounds better as I don't use the auth feature of supabase
but I'm not sure then how I will be able to validate policies
Then you use auth.jwt()->>'sub'
Where sub is a string.
Compare that to a string id column in the tables.
As far as I understand auth.jwt() is the raw jwt decoded payload, it's unverified
Maybe I misunderstood
The jwt is verified before auth.jwt() can use it.
Oh so I'm free to use the auth.jwt fields in terms of security?
Yes. The JWT is verified by the signing key and all the claims are added to current settings by PostgREST. Then auth.jwt() reads those.
Like for instance I have a buckets with paths of user ids
I want to verify that user can upload only to his own bucket
Storage and Realtime use the same.
so I configured the jwt with userid in the client using the custom callback
and I just need to access userid from the verified jwt object in the RLS policy
Storage made a change ages ago because owner used to be just UUID from sub. Now there is a text version also.
Interesting. I wonder then why you initially suggested trying to use the auth.uid()?
Because I did not know your auth provider was not use a UUID.
Oh yea it's pretty odd
And it would be cleaner to use auth.uid()
Not sure about the syntax yet, will look into it. but I guess I can create some migration with helper function like
betterauth_userid()
that access the auth.jwt().user_id somehow and use it across all the RLS policies?I would still use the sub claim for the user id even if text. I think that is needed for Storage to put it in the owner_id column.

That is what Clerk does. They have/had a function to get the id from current_settings.
btw no matter what I tried I couldn't access it from current_settings
only from auth.jwt()
PostgREST 13.0
Transactions
After User Impersonation, every request to an API resource runs inside a transaction. The sequence of the transaction is as follows: Access Mode: The access mode determines whether the transaction can modify the database or not. There are 2 possible values: READ ONLY and READ WRITE. Modifying the...
They all get set into current settings for DB operations. I believe storage emulates that.
you mean like I should try to be close to how it works with the regular format of supabase auth?
I would just use auth.jwt...
But if you want the code I just linked gets you one step closer as that is what auth.jwt() calls.
Nice
so the JWT is even verified at postgres level
https://github.com/supabase/auth/blob/86b7de45c9432ea6ee9bd7c7e9cfe96e038fe2bc/migrations/20220531120530_add_auth_jwt_function.up.sql#L1
Auth is not actually involved in this. PostgREST (the db REST API), storage and realtime verify/decode the jwt claims into current settings as PostgREST does.
btw if I use auth.jwt() then I don't need special fields, like I can even just pass only user_id? (whatever I want)
You have to have some minimum claims in the JWT for it to get validated.
Role certainly.
This is postgres requirement?
Postgres knows nothing about JWTs
PostgREST does.
It uses the role claim to set the user role to do the SQL request. This is anon,authenticated or service_role by default.
Nice
I will play with that and try to implement it now by using the auth.jwt()
thank you! really appreciate your help.
this is what I ended with




Will be cool to add minimal example for how to use 3rd auths + RLS to the docs of Supabase
You could do a PR. I don't know if they have the bandwidth to add that to the docs.
do you think this appraoch (which sadly i will have to use) is not totally stable with the othe features of supabase?
or that it should work fine with the buckets/database since it's basic postgrest concepts
Sorry, I've only helped people doing this. I've never done custom JWT signing with SB myself.
Realtime and Storage do not use PostgREST, but emulate the same methods to set the current_settings with the claims.