mirror of
https://github.com/supabase/supabase.git
synced 2026-10-18 07:39:07 +03:00
"To Authenticated" Update custom-claims-and-role-based-access-control-rbac.mdx and added "to authenticated" in the rls policy which only allows logged in users to query instead of non logged in users as per "Specify roles in your policies" found here supabase.com/docs/guides/database/postgres/row-level-security#specify-roles-in-your-policies
282 lines
9.2 KiB
Plaintext
282 lines
9.2 KiB
Plaintext
---
|
|
id: 'custom-claims-and-role-based-access-control-rbac'
|
|
title: 'Custom Claims & Role-based Access Control (RBAC)'
|
|
description: 'Use Auth Hooks to add custom claims for managing role-based access control.'
|
|
---
|
|
|
|
Custom Claims are special attributes attached to a user that you can use to control access to portions of your application. For example:
|
|
|
|
```json
|
|
{
|
|
"user_role": "admin",
|
|
"plan": "TRIAL",
|
|
"user_level": 100,
|
|
"group_name": "Super Guild!",
|
|
"joined_on": "2022-05-20T14:28:18.217Z",
|
|
"group_manager": false,
|
|
"items": ["toothpick", "string", "ring"]
|
|
}
|
|
```
|
|
|
|
To implement Role-Based Access Control (RBAC) with `custom claims`, use a [Custom Access Token Auth Hook](/docs/guides/auth/auth-hooks#hook-custom-access-token). This hook runs before a token is issued. You can use it to add additional claims to the user's JWT.
|
|
|
|
This guide uses the [Slack Clone example](https://github.com/supabase/supabase/tree/master/examples/slack-clone/nextjs-slack-clone) to demonstrate how to add a `user_role` claim and use it in your [Row Level Security (RLS) policies](/docs/guides/database/postgres/row-level-security).
|
|
|
|
## Create a table to track user roles and permissions
|
|
|
|
In this example, you will implement two user roles with specific permissions:
|
|
|
|
- `moderator`: A moderator can delete all messages but not channels.
|
|
- `admin`: An admin can delete all messages and channels.
|
|
|
|
```sql supabase/migrations/init.sql
|
|
-- Custom types
|
|
create type public.app_permission as enum ('channels.delete', 'messages.delete');
|
|
create type public.app_role as enum ('admin', 'moderator');
|
|
|
|
-- USER ROLES
|
|
create table public.user_roles (
|
|
id bigint generated by default as identity primary key,
|
|
user_id uuid references auth.users on delete cascade not null,
|
|
role app_role not null,
|
|
unique (user_id, role)
|
|
);
|
|
comment on table public.user_roles is 'Application roles for each user.';
|
|
|
|
-- ROLE PERMISSIONS
|
|
create table public.role_permissions (
|
|
id bigint generated by default as identity primary key,
|
|
role app_role not null,
|
|
permission app_permission not null,
|
|
unique (role, permission)
|
|
);
|
|
comment on table public.role_permissions is 'Application permissions for each role.';
|
|
```
|
|
|
|
<Admonition type="note">
|
|
|
|
For the [full schema](https://github.com/supabase/supabase/blob/master/examples/slack-clone/nextjs-slack-clone/README.md), see the example application on [GitHub](https://github.com/supabase/supabase/tree/master/examples/slack-clone/nextjs-slack-clone).
|
|
|
|
</Admonition>
|
|
|
|
You can now manage your roles and permissions in SQL. For example, to add the mentioned roles and permissions from above, run:
|
|
|
|
```sql supabase/seed.sql
|
|
insert into public.role_permissions (role, permission)
|
|
values
|
|
('admin', 'channels.delete'),
|
|
('admin', 'messages.delete'),
|
|
('moderator', 'messages.delete');
|
|
```
|
|
|
|
## Create Auth Hook to apply user role
|
|
|
|
The [Custom Access Token Auth Hook](/docs/guides/auth/auth-hooks#hook-custom-access-token) runs before a token is issued. You can use it to edit the JWT.
|
|
|
|
<Tabs
|
|
scrollable
|
|
size="small"
|
|
type="underlined"
|
|
defaultActiveId="plpgsql"
|
|
queryGroup="language"
|
|
>
|
|
<TabPanel id="plpgsql" label="PL/pgSQL (best performance)">
|
|
|
|
```sql supabase/migrations/auth_hook.sql
|
|
-- Create the auth hook function
|
|
create or replace function public.custom_access_token_hook(event jsonb)
|
|
returns jsonb
|
|
language plpgsql
|
|
stable
|
|
as $$
|
|
declare
|
|
claims jsonb;
|
|
user_role public.app_role;
|
|
begin
|
|
-- Fetch the user role in the user_roles table
|
|
select role into user_role from public.user_roles where user_id = (event->>'user_id')::uuid;
|
|
|
|
claims := event->'claims';
|
|
|
|
if user_role is not null then
|
|
-- Set the claim
|
|
claims := jsonb_set(claims, '{user_role}', to_jsonb(user_role));
|
|
else
|
|
claims := jsonb_set(claims, '{user_role}', 'null');
|
|
end if;
|
|
|
|
-- Update the 'claims' object in the original event
|
|
event := jsonb_set(event, '{claims}', claims);
|
|
|
|
-- Return the modified or original event
|
|
return event;
|
|
end;
|
|
$$;
|
|
|
|
grant usage on schema public to supabase_auth_admin;
|
|
|
|
grant execute
|
|
on function public.custom_access_token_hook
|
|
to supabase_auth_admin;
|
|
|
|
revoke execute
|
|
on function public.custom_access_token_hook
|
|
from authenticated, anon, public;
|
|
|
|
grant all
|
|
on table public.user_roles
|
|
to supabase_auth_admin;
|
|
|
|
revoke all
|
|
on table public.user_roles
|
|
from authenticated, anon, public;
|
|
|
|
create policy "Allow auth admin to read user roles" ON public.user_roles
|
|
as permissive for select
|
|
to supabase_auth_admin
|
|
using (true)
|
|
```
|
|
|
|
</TabPanel>
|
|
<TabPanel id="plv8" label="JavaScript (PLV8)">
|
|
|
|
```sql supabase/migrations/auth_hook.sql
|
|
-- Enable the "plv8" extension
|
|
create extension plv8;
|
|
|
|
-- Create the auth hook function
|
|
create or replace function custom_access_token_hook(event jsonb)
|
|
returns jsonb
|
|
language plv8
|
|
as $$
|
|
var user_role;
|
|
|
|
// Fetch the current user's user_role from the public user_roles table.
|
|
var result = plv8.execute("select role from public.user_roles where user_id = $1", [event.user_id]);
|
|
if (result.length > 0) {
|
|
user_role = result[0].role;
|
|
} else {
|
|
// Assign null
|
|
user_role = null;
|
|
}
|
|
|
|
// Check if 'claims' exists in the event object; if not, initialize it
|
|
if (!event.claims) {
|
|
event.claims = {};
|
|
}
|
|
|
|
// Update the level in the claims
|
|
event.claims.user_role = user_role;
|
|
|
|
return event;
|
|
$$;
|
|
|
|
grant usage on schema public to supabase_auth_admin;
|
|
|
|
grant execute
|
|
on function public.custom_access_token_hook
|
|
to supabase_auth_admin;
|
|
|
|
revoke execute
|
|
on function public.custom_access_token_hook
|
|
from authenticated, anon, public;
|
|
|
|
grant all
|
|
on table public.user_roles
|
|
to supabase_auth_admin;
|
|
|
|
revoke all
|
|
on table public.user_roles
|
|
from authenticated, anon, public;
|
|
|
|
create policy "Allow auth admin to read user roles" ON public.user_roles
|
|
as permissive for select
|
|
to supabase_auth_admin
|
|
using (true)
|
|
```
|
|
|
|
</TabPanel>
|
|
</Tabs>
|
|
|
|
### Enable the hook
|
|
|
|
In the dashboard, navigate to [`Authentication > Hooks (Beta)`](/dashboard/project/_/auth/hooks) and select the appropriate PostgreSQL function from the dropdown menu.
|
|
|
|
When developing locally, follow the [local development](/docs/guides/auth/auth-hooks#local-development) instructions.
|
|
|
|
<Admonition type="note">
|
|
|
|
To learn more about Auth Hooks, see the [Auth Hooks docs](/docs/guides/auth/auth-hooks).
|
|
|
|
</Admonition>
|
|
|
|
## Accessing custom claims in RLS policies
|
|
|
|
To utilize Role-Based Access Control (RBAC) in Row Level Security (RLS) policies, create an `authorize` method that reads the user's role from their JWT and checks the role's permissions:
|
|
|
|
```sql supabase/migrations/init.sql
|
|
create or replace function public.authorize(
|
|
requested_permission app_permission
|
|
)
|
|
returns boolean as $$
|
|
declare
|
|
bind_permissions int;
|
|
user_role public.app_role;
|
|
begin
|
|
-- Fetch user role once and store it to reduce number of calls
|
|
select (auth.jwt() ->> 'user_role')::public.app_role into user_role;
|
|
|
|
select count(*)
|
|
into bind_permissions
|
|
from public.role_permissions
|
|
where role_permissions.permission = requested_permission
|
|
and role_permissions.role = user_role;
|
|
|
|
return bind_permissions > 0;
|
|
end;
|
|
$$ language plpgsql stable security definer set search_path = '';
|
|
```
|
|
|
|
<Admonition type="note">
|
|
|
|
You can read more about using functions in RLS policies in the [RLS guide](/docs/guides/database/postgres/row-level-security#using-functions).
|
|
|
|
</Admonition>
|
|
|
|
You can then use the `authorize` method within your RLS policies. For example, to enable the desired delete access, you would add the following policies:
|
|
|
|
```sql
|
|
create policy "Allow authorized delete access" on public.channels for delete to authenticated using ( (SELECT authorize('channels.delete')) );
|
|
create policy "Allow authorized delete access" on public.messages for delete to authenticated using ( (SELECT authorize('messages.delete')) );
|
|
```
|
|
|
|
## Accessing custom claims in your application
|
|
|
|
The auth hook will only modify the access token JWT but not the auth response. Therefore, to access the custom claims in your application, e.g. your browser client, or server-side middleware, you will need to decode the `access_token` JWT on the auth session.
|
|
|
|
In a JavaScript client application you can for example use the [`jwt-decode` package](https://www.npmjs.com/package/jwt-decode):
|
|
|
|
```js
|
|
import { jwtDecode } from 'jwt-decode'
|
|
|
|
const { subscription: authListener } = supabase.auth.onAuthStateChange(async (event, session) => {
|
|
if (session) {
|
|
const jwt = jwtDecode(session.access_token)
|
|
const userRole = jwt.user_role
|
|
}
|
|
})
|
|
```
|
|
|
|
For server-side logic you can use packages like [express-jwt](https://github.com/auth0/express-jwt), [koa-jwt](https://github.com/stiang/koa-jwt), [PyJWT](https://github.com/jpadilla/pyjwt), [dart_jsonwebtoken](https://pub.dev/packages/dart_jsonwebtoken), [Microsoft.AspNetCore.Authentication.JwtBearer](https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.JwtBearer), etc.
|
|
|
|
## Conclusion
|
|
|
|
You now have a robust system in place to manage user roles and permissions within your database that automatically propagates to Supabase Auth.
|
|
|
|
## More resources
|
|
|
|
- [Auth Hooks](/docs/guides/auth/auth-hooks)
|
|
- [Row Level Security](/docs/guides/database/postgres/row-level-security)
|
|
- [RLS Functions](/docs/guides/database/postgres/row-level-security#using-functions)
|
|
- [Next.js Slack Clone Example](https://github.com/supabase/supabase/tree/master/examples/slack-clone/nextjs-slack-clone)
|