
# 

Custom Claims & Role-based Access Control (RBAC)

* * *

Custom Claims are special attributes attached to a user that you can use to control access to portions of your application. For example:

```
1{2  "user_role": "admin",3  "plan": "TRIAL",4  "user_level": 100,5  "group_name": "Super Guild!",6  "joined_on": "2022-05-20T14:28:18.217Z",7  "group_manager": false,8  "items": ["toothpick", "string", "ring"]9}
```

To implement Role-Based Access Control (RBAC) with `custom claims`, use a [Custom Access Token Auth Hook](../../auth/auth-hooks/index.md#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](../../database/postgres/row-level-security/index.md).

## Create a table to track user roles and permissions[#](#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.

```
1-- Custom types2create type public.app_permission as enum ('channels.delete', 'messages.delete');3create type public.app_role as enum ('admin', 'moderator');45-- USER ROLES6create table public.user_roles (7  id        bigint generated by default as identity primary key,8  user_id   uuid references auth.users on delete cascade not null,9  role      app_role not null,10  unique (user_id, role)11);12comment on table public.user_roles is 'Application roles for each user.';1314-- ROLE PERMISSIONS15create table public.role_permissions (16  id           bigint generated by default as identity primary key,17  role         app_role not null,18  permission   app_permission not null,19  unique (role, permission)20);21comment on table public.role_permissions is 'Application permissions for each role.';
```

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).

You can now manage your roles and permissions in SQL. For example, to add the mentioned roles and permissions from above, run:

```
1insert into public.role_permissions (role, permission)2values3  ('admin', 'channels.delete'),4  ('admin', 'messages.delete'),5  ('moderator', 'messages.delete');
```

## Create Auth Hook to apply user role[#](#create-auth-hook-to-apply-user-role)

The [Custom Access Token Auth Hook](../../auth/auth-hooks/index.md#hook-custom-access-token) runs before a token is issued. You can use it to edit the JWT.

```
1-- Create the auth hook function2create or replace function public.custom_access_token_hook(event jsonb)3returns jsonb4language plpgsql5stable6as $$7  declare8    claims jsonb;9    user_role public.app_role;10  begin11    -- Fetch the user role in the user_roles table12    select role into user_role from public.user_roles where user_id = (event->>'user_id')::uuid;1314    claims := event->'claims';1516    if user_role is not null then17      -- Set the claim18      claims := jsonb_set(claims, '{user_role}', to_jsonb(user_role));19    else20      claims := jsonb_set(claims, '{user_role}', 'null');21    end if;2223    -- Update the 'claims' object in the original event24    event := jsonb_set(event, '{claims}', claims);2526    -- Return the modified or original event27    return event;28  end;29$$;3031grant usage on schema public to supabase_auth_admin;3233grant execute34  on function public.custom_access_token_hook35  to supabase_auth_admin;3637revoke execute38  on function public.custom_access_token_hook39  from authenticated, anon, public;4041grant all42  on table public.user_roles43to supabase_auth_admin;4445revoke all46  on table public.user_roles47  from authenticated, anon, public;4849create policy "Allow auth admin to read user roles" ON public.user_roles50as permissive for select51to supabase_auth_admin52using (true);
```

### Enable the hook[#](#enable-the-hook)

In the dashboard, navigate to [`Authentication > Hooks (Beta)`](/dashboard/project/_/auth/hooks) and select the appropriate Postgres function from the dropdown menu.

When developing locally, follow the [local development](../../auth/auth-hooks/index.md#local-development) instructions.

To learn more about Auth Hooks, see the [Auth Hooks docs](../../auth/auth-hooks/index.md).

## Accessing custom claims in RLS policies[#](#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:

```
1create or replace function public.authorize(2  requested_permission app_permission3)4returns boolean as $$5declare6  bind_permissions int;7  user_role public.app_role;8begin9  -- Fetch user role once and store it to reduce number of calls10  select (auth.jwt() ->> 'user_role')::public.app_role into user_role;1112  select count(*)13  into bind_permissions14  from public.role_permissions15  where role_permissions.permission = requested_permission16    and role_permissions.role = user_role;1718  return bind_permissions > 0;19end;20$$ language plpgsql stable security definer set search_path = '';
```

You can read more about using functions in RLS policies in the [RLS guide](../../database/postgres/row-level-security/index.md#using-functions).

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:

```
1create policy "Allow authorized delete access" on public.channels for delete to authenticated using ( (SELECT authorize('channels.delete')) );2create policy "Allow authorized delete access" on public.messages for delete to authenticated using ( (SELECT authorize('messages.delete')) );
```

## Accessing custom claims in your application[#](#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):

```
1import { jwtDecode } from 'jwt-decode'23const { subscription: authListener } = supabase.auth.onAuthStateChange(async (event, session) => {4  if (session) {5    const jwt = jwtDecode(session.access_token)6    const userRole = jwt.user_role7  }8})
```

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[#](#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[#](#more-resources)

*   [Auth Hooks](../../auth/auth-hooks/index.md)
*   [Row Level Security](../../database/postgres/row-level-security/index.md)
*   [RLS Functions](../../database/postgres/row-level-security/index.md#using-functions)
*   [Next.js Slack Clone Example](https://github.com/supabase/supabase/tree/master/examples/slack-clone/nextjs-slack-clone)
