Demo, all content is generated
Question

Claude put is_admin in user_metadata, can a user change that themselves?

Solved · 3303 views · asked by wairimu · edited

I asked for an admin role. The AI stores it at signup like

supabase.auth.updateUser({ data: { is_admin: false } })

and my admin pages check user.user_metadata.is_admin. It works: I set myself to true in the dashboard and I see the admin panel. But now I wonder... if the app can call updateUser, can a user too?

What I’ve tried

Searched the Supabase docs for user_metadata, found something about it being 'modifiable by the user' but not sure it means what I think.

Comment
I literally have the same code. Thank you for asking this. hugo_l · edited

3 answers

Marked as helpful by the asker
mira_dev · edited

It means exactly what you think. Any logged in user can open the browser console and run

await supabase.auth.updateUser({ data: { is_admin: true } })

and they're admin. user_metadata is for things like display name.

Two safe options:

A. app_metadata, which only the service role can change. Set it from the dashboard SQL editor or a server function:

update auth.users
set raw_app_meta_data = raw_app_meta_data || '{"role":"admin"}'
where email = 'you@example.com';

It ends up in the JWT, so you can use it in RLS: (auth.jwt() -> 'app_metadata' ->> 'role') = 'admin'.

B. An admins table with RLS that users can't write to. Easier to manage when you have more roles.

And whatever you choose: the admin data must be protected by RLS too, not just the admin pages.

Comment
Tested the console trick on my own app with a normal account. I became admin. Switching to app_metadata tonight. wairimu · edited
Great answer. After changing app_metadata the user needs a fresh token (log out/in) before the JWT has the new role, which surprises people. amir_h · edited
hannah_reyes · edited

When you outgrow a single flag: Supabase has a Custom Access Token Hook, a Postgres function that adds claims (like user_role) to every JWT from your own roles table. RLS then reads auth.jwt() ->> 'user_role'. Roles stay in a normal table you can manage.

Comment
sven_fire · edited

Same trap exists in Firebase for anyone reading: never trust a role a client can write. Firebase uses custom claims set by the Admin SDK, which is the equivalent of app_metadata.

Comment