Redwood and Supabase Auth Example

Hi All,

I completed the full tutorial (parts 1 and 2) and switched out Supabase Auth for Netlify Identity. It is a pretty basic example but should help someone get up and running with Supabase and Redwood?

I built a super quick and dirty modal for the login/logout with Supabase - please don’t use this code in production as it needs work from an a11y perspective before it would be ready.

The big drawback is that I removed the roles functionality from the tutorial code because Supabase does not yet seem to have an easy way to implement roles like Netlify does. I am pretty sure this could be done by setting up a user table that was connected to the auth table - but will leave that for others to demo.

1 Like

Hi Eric and thanks for trying out RBAC in both Supabase and Netlify!

I am working on a Supabase-based app currently and will encounter this scenario soon, too.

Unlike Auth0 and Netlify, Supabase doesn’t send user role or permissions on the access_token.

Supabase has great security in the way of roles and policies that ensure only allowed actors can access or interact with your data at level specific level.

It’s important to recognize that when Prisma connects to the database it is doing so with service_role permission – it can query, delete, add, drop a table etc. Thus the app middleware needs to enforce things. Supabase’s js client passes along the JWT and then determines their PG role and policies from it and enforces what the can happen at a low level.

But, we don’t/can’t? set that JWT when querying – though it would be great if we can. I happened to run across this discussion yesterday asking for ’ Support for Postgres’ SET command in raw mode/PG RLS policies in general`.

But until then … :wink:

For example, the anon roles can’t do things that an authenticated roles can.

But - (as I understand thing) this is all done/setup/enforced at the Postgres database level.

There isn’t necessarily a mapping between this database role/policy and a user “app/business” role (ie, author) and permission (can add article – insert on articles, but not publish aka update the status).

Actually perhaps Paul @kiwicopple can help here to suggest the best way we might capture these “user roles” and map them (if needed to policies … since we don’t necessarily need to use them:

Policies are a tool. In the case of “serverless/Jamstack” setups, they are especially effective because you don’t have to deploy any middleware at all.

What is supa-nice is that users are in a separate auth schema and perhaps one can store UserRole there and maybe map to a user and some underlying role/policies?

This also make it harder for any user info to leak out via queries or GraphQL nested resolvers.

We’d have to make the getCurrentUser do a query per transaction (and maybe cache in context) to do a rawQuery to join in on the auth schema and User- > UserRole with the sub user id (Prisma can only query the public schema, not across schemas … which is another issue.

Any ideas @kiwicopple ? As I said, I am going to get into RBAC w/ RW and Supabase w/in the next week or two so would love to figure out a solid pattern and add to the RW Cookbook.

1 Like

@dthyresson Thank-you for such a thorough response! I will look forward to the cookbook/example you can share with the community in the coming weeks.

I am really excited about the potential Redwood + Supabase offers to solo-developers like me! I’ll keep you posted if I manage to discover anything along the way but I am probably going to just build out some ideas to learn RW better and then worry about the auth provider I use at a later date. I really like the idea that with Supabase I can manage all those “backend” services in one spot and would prefer to use them if I can!

I just happened across some RLS and JWT docs in postgraphile:

https://www.graphile.org/postgraphile/security/#how-it-works

begin;
set local role app_user;
set local jwt.claims.role to 'app_user';
set local jwt.claims.user_id to '2';

-- WE PERFORM GRAPHQL QUERIES HERE

commit;

If that could be set when Prisma runs that would be wonderful as would set that to have RLS.

2 Likes

@dthyresson Yeah I have been searching around for ways to implement this with Prisma. It looks like they have an open issue. Might also be worth throwing your support on the issue in the prisma repo Supporting Postgres' SET across queries of a request · Issue #5128 · prisma/prisma · GitHub

1 Like

Thanks for the tag @dthyresson

Can you elaborate a bit more on this one? Do you want to add content into the JWT, so that you can access it on the client? If that is the case then you can add it either to auth.users.raw_app_metadata or auth.users.raw_user_metadata - these will both be encoded into the JWT when the user session is created

Perhaps we work together to come up with a “definitive” example for this one? Now that we are past Launch Week I have some time to build up our examples. There are a few different ways to handle RBAC inside your app, and so it will be good to put our brains together to figure out which is the most viable option for most developers (here is a good article on RBAC with Policies: Medium)

@kiwicopple and @dthyresson having an official example app would be great. As a front-end person who has played around with serverless functions and some “mid-stack” stuff I am loving the way that combining Supabase and Redwood is gradually exposes me to more backend thinking/tech.

The starting point I posted above is from the end of the second tutorial and I just swapped out the Netlify Auth for Supabase Auth (minus RBAC as stated above).

Hey All,

While I do really look forward to supabase’s row level secuirty to having Prisma support (or the other way around), one way you could implement RBAC is by storing roles in the DB. As described here: Cookbook - Role-based Access Control (RBAC) : RedwoodJS Docs

This is how I’m doing (very limited) RBAC on my project. I’m using enums for Roles, and its really helpful with typescript

enum Roles {
  OWNER
  ADMIN
  MEMBER
  VIEWER
}

model TeamMember {
  id     String  @id @default(cuid())
  user   User    @relation(fields: [userId], references: [id])
  userId String
  role   Roles
#. hide other attributes for brevity
}

I’d really welcome that @kiwicopple.

I should have started with the what and why and not the how.

  • Have a way to assign “app/business” user roles to users
  • That that made available in the RW’s currentUser’s user_metadata (either from JWT direct or enriched with info and set) so that the web side can determine if the user “hasRole()” to gain access to protected areas of the app

If that is the case then you can add it either to auth.users.raw_app_metadata or auth.users.raw_user_metadata - these will both be encoded into the JWT when the user session is created

  • when querying/updated data via Prisma, have some way of identifying the user so that RLS and policies can still be enforced at the PG level

Some points to know:

  • Prisma can query across schemas, so it’s not that easy to join the public and auth schemas in Supabase. You have to compose the SQL yourself via a rawQuery

Maybe we adapt the Blog w/ RBAC Tutorial that currently uses Netlify but a fork that uses Supabase PG and Auth instead? I could even add storage for photos next :wink:

The Uses Netlify Identity Trigger Serverless function calls to assign roles when signing up feature could also showcase some upcoming SB features when a new user is created (hint).

1 Like

Heyho, a wild Prisma (employee) appears in this thread 3 weeks after it got pointed out to him as he forgot about the tab… seriously. Hi!

My knowledge about RLS with Supabase is minimal, but while this tab was waiting to be rediscovered we at Prisma found a possible temporary workaround for how to execute the SET queries reliably before sending actual queries: github.com/prisma/prisma/issues/5128#issuecomment-826679950 But we need your help to confirm that would actually work. (If it does, we could further build another workaround with middlewares to make it easy to write all queries like that.)

Could you maybe take a look at this and see if this makes sense?

(And then of course we hope to get proper support for this into the Prisma Client API)

2 Likes

Thanks @janpio for this update an example:

const [ignore, userList, updateUser] = await prisma.$transaction([
  prisma.$executeRaw(`SET current_user_id = ${currentUser.id}`),
  prisma.post.findMany({ where: { title: { contains: 'prisma' } } }),
  prisma.post.count(),
])

@kiwicopple I looked at the RLS and policy docs here: Auth | Supabase and

Supabase provides a special function in Postgres, auth.uid(), which extracts the user’s UID from the JWT. This is especially useful when creating Policies.

But since it looks like that relies on the request:

CREATE OR REPLACE FUNCTION auth.uid()
 RETURNS uuid
 LANGUAGE sql
 STABLE
AS $function$
  select nullif(current_setting('request.jwt.claim.sub', true), '')::uuid;
$function$

perhaps

create policy "Users can update their own profiles." 
  on profiles for update using (
    auth.uid() = id
  );

would be

create policy "Users can update their own profiles." 
  on profiles for update using (
   current_user_id = id
  );

given the above Prisma example when setting current_user_id ahead of the query?

I guess I’ll have to figure out a good way of testing this – will try this week.