Hoppa till innehållet

Supabase raw_user_meta_data: så blev en registrering Owner

Supabase

Visa som MarkdownÖppna i ClaudeÖppna i ChatGPTÖppna i Perplexity

Frågan kopieras också. Om chatten öppnas tom, klistra in den.

Kort sagt

raw_user_meta_data innehåller vad webbläsaren skickade till supabase.auth.signUp(). I en produktionsapp läste triggern handle_new_user den nya användarens roll därifrån, så vem som helst kunde registrera sig med Owner-rollens id och äga kontot. Åtgärden: rollen kommer från en tabell som bara servern skriver till, ifylld innan inbjudan skickas.

Det här hände i tidrapporteringssystemet för revisionsbyrån, en Supabase-app där varje åtkomstregel ligger i databasen. Buggen satt i triggern som skapar en profil för varje ny användare.

Vad gjorde triggern?

Förenklad såg den ut så här:

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;
$$;

Inbjudningsflödet la rätt role_id där, så vid normal användning fungerade det. Men raw_user_meta_data är options.data från en registrering, och webbläsaren bestämmer vad som ligger i den:

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

Behörighetskontrollen returnerade sant för ägare innan den tittade på något annat, så det enda fältet gav full kontroll över kontot. Supabases egen Users-guide säger att användarmetadata inte får användas i RLS-policyer eller auktoriseringslogik, eftersom användaren kan ändra den utan några kontroller.

Varifrån en ny användares roll kom. Triggern handle_new_user före och efter åtgärden. Före: vem som helst som registrerar sig: signUp options.data → raw_user_meta_data → handle_new_user → Owner-roll; Efter: bara inbjudna: Inbjudningsfunktion → pending_invites → handle_new_user → Inbjuden roll.
Från registreringstriggern i en Supabase-app i produktion, juni 2026.

Vad åtgärdade det?

Rollen kommer nu från en privat tabell som klienten inte kommer åt:

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;

Inbjudningsfunktionen körs på servern, efter att den som bjuder in har fått sin behörighet kontrollerad. Den skriver först den väntande inbjudan och skapar sedan inbjudningslänken. Triggern slår upp rollen via e-postadressen och raderar raden. Namnet kommer fortfarande från metadata, vilket är i sin ordning: det är visningsdata.

Ordningen spelar roll. En inbjudan skapar användaren direkt: Supabases Users-guide noterar att en inbjudan till en ny e-postadress skapar en obekräftad användare. Triggern körs i just det ögonblicket, så den väntande raden måste redan finnas.

Hur kontrollerar du din egen app?

Öppna din handle_new_user-funktion, eller vilken trigger som helst på auth.users, och sök efter raw_user_meta_data. Namn och avatar därifrån är i sin ordning. Roll, företag, plan, tenant eller något annat som ger åtkomst måste komma från en tabell eller från raw_app_meta_data, som användaren inte kan ändra.

Prova sedan: registrera dig via API:et med role_id eller role: 'admin' i options.data. Det nya kontot ska inte få något extra.

Den här sortens kontroll är första steget i min RLS-testmetod. För multi-tenant-appar, se också vad RLS missar för en användare i två organisationer. De andra kontrollerna jag kör på varje Supabase-app finns i min säkerhetschecklista för Lovable-appar. Det första jag tittar på i dashboarden är vilka scheman Data API:et exponerar. Supabase säkerhetskontroll täcker roller, behörigheter, policyer och auth-inställningar mot 45 OWASP Top 10:2025-regler, inte vad en trigger kopierar från registreringsmetadata, så det här är fortfarande en manuell kontroll.

© 2026 Denys Zaichenko. Originalartikel: https://madsalt.tech/sv/notes/supabase-raw-user-meta-data-role/