Přejít na obsah

Supabase raw_user_meta_data: jak se registrace stala Ownerem

Supabase

Zobrazit jako MarkdownOtevřít v ClaudeOtevřít v ChatGPTOtevřít v Perplexity

Otázka se zkopíruje také. Pokud se chat otevře prázdný, vložte ji.

TL;DR

raw_user_meta_data obsahuje to, co prohlížeč předal do supabase.auth.signUp(). V jedné produkční aplikaci z něj trigger handle_new_user četl roli nového uživatele, takže se kdokoli mohl zaregistrovat s id role Owner a převzít účet. Oprava: role pochází z tabulky, kterou zapisuje jen server, vyplněné dřív, než se pozvánka odešle.

Stalo se to v evidenci času pro auditorskou firmu, aplikaci na Supabase, kde každé přístupové pravidlo žije v databázi. Chyba seděla v triggeru, který vytváří profil pro každého nového uživatele.

Co trigger dělal?

Zjednodušeně vypadal takto:

create function public.handle_new_user()
returns trigger
language plpgsql
security definer set search_path = ''
as $$
declare
  _role_id uuid := (new.raw_user_meta_data ->> 'role_id')::uuid;
begin
  insert into public.profiles (id, full_name, role_id)
  values (new.id, new.raw_user_meta_data ->> 'full_name', _role_id);
  return new;
end;
$$;

Pozvánkový tok sem vkládal správné role_id, takže při běžném použití to fungovalo. Jenže raw_user_meta_data jsou options.data registrace a co v nich bude, rozhoduje prohlížeč:

await supabase.auth.signUp({
  email: 'me@example.com',
  password: '...',
  options: { data: { role_id: '<owner role uuid>' } },
})

Kontrola oprávnění vracela pro vlastníky true dřív, než se podívala na cokoli jiného, takže tohle jediné pole dávalo plnou kontrolu nad účtem. Samotný průvodce Users od Supabase říká, že metadata uživatele se nesmějí používat v politikách RLS ani v autorizační logice, protože je uživatel může upravit bez jakékoli kontroly.

Odkud se vzala role nového uživatele. Trigger handle_new_user před opravou a po ní. Před: kdokoli, kdo se zaregistruje: signUp options.data → raw_user_meta_data → handle_new_user → Role Owner; Po: jen pozvaní lidé: Funkce pozvánky → pending_invites → handle_new_user → Role pozvaného.
Z registračního triggeru produkční aplikace na Supabase, červen 2026.

Co to opravilo?

Role se nově bere ze soukromé tabulky, na kterou klient nesáhne:

create table private.pending_invites (
  email text primary key,
  role_id uuid,
  invited_by uuid,
  created_at timestamptz default now()
);
revoke all on table private.pending_invites from anon, authenticated;

Funkce pozvánky běží na serveru a kontroluje oprávnění zvoucího. Nejdřív zapíše čekající pozvánku a teprve potom vytvoří odkaz. Trigger vyhledá roli podle e-mailu a řádek smaže. Jméno se pořád bere z metadat, což je v pořádku: jsou to údaje k zobrazení.

Na pořadí záleží. Pozvánka vytvoří uživatele okamžitě: průvodce Users od Supabase uvádí, že pozvání nového e-mailu vytvoří nepotvrzeného uživatele. Trigger se spustí v ten okamžik, takže čekající řádek už musí existovat.

Jak zkontrolovat vlastní aplikaci?

Otevřete svou funkci handle_new_user nebo jakýkoli trigger na auth.users a vyhledejte raw_user_meta_data. Jméno a avatar odtud jsou v pořádku. Role, firma, tarif, tenant nebo cokoli jiného, co uděluje přístup, musí pocházet z tabulky nebo z raw_app_meta_data, které uživatel měnit nemůže.

Pak to vyzkoušejte: zaregistrujte se přes API s role_id nebo role: 'admin' v options.data. Nový účet nesmí dostat nic navíc.

Takováto kontrola je prvním krokem mé metody testování RLS. U multi-tenant aplikací se podívejte i na co RLS přehlédne u uživatele ve dvou org. Další kontroly, které spouštím u každé aplikace na Supabase, jsou v mém checklistu zabezpečení aplikace z Lovable. První, na co se v dashboardu dívám, je která schémata Data API vystavuje. Kontrola zabezpečení Supabase pokrývá role, granty, politiky a nastavení autentizace podle 45 pravidel OWASP Top 10:2025, ne to, co trigger kopíruje z metadat při registraci, takže tato kontrola zůstává ruční.

© 2026 Denys Zaichenko. Původní článek: https://madsalt.tech/cs/notes/supabase-raw-user-meta-data-role/