Want to know more about Custom Claims? See the FAQ below.
The file install.sql contains all the PostgreSQL functions you need to implement and manage custom claims in your Supabase project.
- Paste the SQL code from install.sql into the SQL Query Editor of your Supabase project.
- Click
RUN
to execute the code.
- Paste the SQL code from uninstall.sql into the SQL Query Editor of your Supabase project.
- Click
RUN
to execute the code.
If you want to tighten security so that custom claims can only be set or deleted from inside the query editor or inside your PostgreSQL functions or triggers, edit the function is_claims_admin()
to disallow usage by app users (no usage through the API / Postgrest). Instructions are included in the function.
By default, usage is allowed through your API, but the ability to set or delete claims is restricted to only users who have the claims_admin
custom claim set to true
. This allows you to create an "admin" section of your app that allows designated users to modify custom claims for other users of your app.
If the only way to set or delete claims requires the claims_admin
claim to be set to true
and no users have that claim, how can I edit custom claims from within my app?
The answer is to "bootstrap" a user by running the following command inside your Supabase Query Editor window:
select set_claim('03acaa13-7989-45c1-8dfb-6eeb7cf0b92e', 'claims_admin', 'true');
where 03acaa13-7989-45c1-8dfb-6eeb7cf0b92e
is the id
of your admin user found in auth.users
.
You can get, set, and delete claims for any user based on the user's id
(uuid) with the following functions:
select get_claims('03acaa13-7989-45c1-8dfb-6eeb7cf0b92e');
| get_claims |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| {"provider": "email", "userrole": "MANAGER", "providers": ["email"], "userlevel": 100, "useractive": true, "userjoined": "2022-05-20T14:07:27.742Z", "claims_admin": true} |
select get_claim('03acaa13-7989-45c1-8dfb-6eeb7cf0b92e', 'userlevel');
| get_claim |
| --------- |
| 100 |
Set a number value. (Note value
is passed as a jsonb
value, so to set a number we need to pass it as a simple string.)
select set_claim('03acaa13-7989-45c1-8dfb-6eeb7cf0b92e', 'userlevel', '200');
Set a text value. (Note value
is passed as a jsonb
value, so to set a number we need to pass it with double-quotes.)
select set_claim('03acaa13-7989-45c1-8dfb-6eeb7cf0b92e', 'userrole', '"MANAGER"');
Common Mistake: If you forget the double-quotes for a string, and try to do this: select set_claim('03acaa13-7989-45c1-8dfb-6eeb7cf0b92e', 'userrole', 'MANAGER');
, the result will be an error: invalid input syntax for type json
Set a boolean value.
select set_claim('03acaa13-7989-45c1-8dfb-6eeb7cf0b92e', 'useractive', 'true');
Set an array value.
select set_claim('03acaa13-7989-45c1-8dfb-6eeb7cf0b92e', 'items', '["bread", "cheese", "butter"]');
Set a complex, nested json / object value.
select set_claim('03acaa13-7989-45c1-8dfb-6eeb7cf0b92e', 'gamestate', '{"level": 5, "items": ["knife", "gun"], "position":{"x": 15, "y": 22}}');
| set_claim |
| --------- |
| OK |
select delete_claim('03acaa13-7989-45c1-8dfb-6eeb7cf0b92e', 'gamestate');
| delete_claim |
| ------------ |
| OK |
When using custom claims from inside a PostgreSQL function or trigger, you can use any of the functions shown in the section above: Inside the Query Editor
.
In addition, you can use the following functions that are specific to the currently logged-in user:
select is_claims_admin();
| is_claims_admin |
| --------------- |
| true |
select get_my_claims();
| get_my_claims |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| {"provider": "email", "userrole": "MANAGER", "providers": ["email"], "userlevel": 100, "useractive": true, "userjoined": "2022-05-20T14:07:27.742Z", "claims_admin": true} |
select get_my_claim('userlevel');
| get_my_claim |
| ------------ |
| 100 |
To use custom claims in an RLS Policy, you'll normally use the get_my_claim
to check a specific claim for the currently logged in user.
get_my_claim('userrole') = '"MANAGER"'
(which the UI will change into the more formal):
((get_my_claim('userrole'::text)) = '"MANAGER"'::jsonb)
coalesce(get_my_claim('userlevel')::numeric,0) > 100
coalesce(get_my_claim('claims_admin')::bool,false)
Here are some sample functions that can be used by any authenticated (logged-in) user of your application:
public get_my_claims = async () => {
const { data, error } = await supabase
.rpc('get_my_claims', {});
return { data, error };
}
public get_my_claim = async (claim: string) => {
const { data, error } = await supabase
.rpc('get_my_claim', {claim});
return { data, error };
}
public is_claims_admin = async () => {
const { data, error } = await supabase
.rpc('is_claims_admin', {});
return { data, error };
}
The following functions can only be used by a "claims admin", that is, a user who has the claims_admin
custom claim set to true
:
(Note: these functions allow you to view, set, and delete claims for any user of your application, so these would be appropriate for an administrative branch of your application to be used only by high-level users with the proper security rights (i.e. claims_admin
level users.))
public get_claims = async (uid: string) => {
const { data, error } = await supabase
.rpc('get_claims', {uid});
return { data, error };
}
public get_claim = async (uid: string, claim: string) => {
const { data, error } = await supabase
.rpc('get_claim', {uid, claim});
return { data, error };
}
public set_claim = async (uid: string, claim: string, value: object) => {
const { data, error } = await supabase
.rpc('set_claim', {uid, claim, value});
return { data, error };
}
public delete_claim = async (uid: string, claim: string) => {
const { data, error } = await supabase
.rpc('delete_claim', {uid, claim});
return { data, error };
}
Custom Claims are special attributes attached to a user that you can use to control access to portions of your application.
For example:
plan: "TRIAL"
user_level: 100
group_name: "Super Guild!"
joined_on: "2022-05-20T14:28:18.217Z"
group_manager: false
items: ["toothpick", "string", "ring"]
Any valid JSON data can be stored in a claim. You can store a string, number, boolean, date (as a string), array, or even a complex, nested, complete JSON object.
Custom claims are stored in the auth.users
table, in the raw_app_meta_data
column for a user.
The Supabase Auth System (GoTrue) currently uses the following custom claims: provider
and providers
, so DO NOT use these. Any other valid string should be ok as the name for your custom claim(s), though.
Performance, mostly. Custom claims are stored in the security token a user receives when logging in, and these claims are made available to the PostgreSQL database as a configuration parameter, i.e. current_setting('request.jwt.claims', true)
. So the database has access to these values immediately without needing to do any disk i/o.
This may sound trivial, but this could have a significant effect on scalability if you use claims in an RLS (Row Level Security) Policy, as it could potentially eliminate thousands (or even millions) of database calls.
One drawback is that claims don't get updated automatically, so if you assign a user a new custom claim, they may need to log out and log back in to have the new claim available to them. The same goes for deleting or changing a claim. So this is not a good tool for storing data that changes frequently.
select * from auth.users where (auth.users.raw_app_meta_data->'claims_admin')::bool = true;
select * from auth.users where (auth.users.raw_app_meta_data->'userleval')::numeric > 100;
(note for strings you need to add double-quotes becuase data is data is stored as JSONB)
select * from auth.users where (auth.users.raw_app_meta_data->'userrole')::text = '"MANAGER"';
The auth.users
table used by Supabase Auth (GoTrue) has both raw_app_meta_data
and a raw_user_meta_data
fields.
raw_user_meta_data
is designed for profile data and can be created and modified by a user. For example, this data can be set when a user signs up: sign-up-with-additional-user-meta-data or this data can be modified by a user with auth-update
raw_app_meta_data
is designed for use by the application layer and is used by GoTrue to handle authentication (For exampple, the provider
and providers
claims are used by GoTrue to track authentication providers.) raw_app_meta_data
is not accessible to the user by default.
https://supabase.com/docs/reference/javascript/auth-api-updateuserbyid#updates-a-users-app_metadata