Gå til indhold

Supabase raw_user_meta_data: sådan blev en tilmelding Owner

Supabase

Vis som MarkdownÅbn i ClaudeÅbn i ChatGPTÅbn i Perplexity

Spørgsmålet kopieres også. Hvis chatten åbner tom, så indsæt det.

TL;DR

raw_user_meta_data indeholder det, browseren sendte til supabase.auth.signUp(). I en produktionsapp læste triggeren handle_new_user den nye brugers rolle derfra, så enhver kunne tilmelde sig med rolle-id’et for Owner og overtage kontoen. Rettelsen: rollen kommer fra en tabel, som kun serveren skriver til, udfyldt før invitationen sendes.

Det skete i tidsregistreringen til et revisionsfirma, en Supabase-app, hvor hver adgangsregel ligger i databasen. Fejlen sad i den trigger, der opretter en profil for hver ny bruger.

Hvad gjorde triggeren?

Forenklet så den sådan ud:

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

Invitationsflowet lagde det rigtige role_id derind, så ved normal brug virkede det. Men raw_user_meta_data er options.data fra en tilmelding, og det er browseren, der bestemmer, hvad der står i den:

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

Rettighedstjekket returnerede true for ejere, før det så på noget andet, så det ene felt gav fuld kontrol over kontoen. Supabases egen Users-guide siger, at brugermetadata ikke må bruges i RLS-policies eller autorisationslogik, fordi brugeren kan redigere dem uden nogen kontrol.

Hvor en ny brugers rolle kom fra. handle_new_user-triggeren før og efter rettelsen. Før: alle, der tilmelder sig: signUp options.data → raw_user_meta_data → handle_new_user → Rollen Owner; Efter: kun inviterede: Invitationsfunktion → pending_invites → handle_new_user → Invitationens rolle.
Fra tilmeldingstriggeren i en Supabase-app i produktion, juni 2026.

Hvad rettede det?

Rollen kommer nu fra en privat tabel, som klienten ikke kan røre:

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;

Invitationsfunktionen kører på serveren, og den inviterendes rettighed bliver tjekket. Den skriver først den ventende invitation og opretter først derefter invitationslinket. Triggeren slår rollen op på e-mailadressen og sletter rækken. Navnet kommer stadig fra metadata, hvilket er fint: det er visningsdata.

Rækkefølgen betyder noget. En invitation opretter brugeren med det samme: Supabases Users-guide skriver, at invitation af en ny e-mailadresse opretter en ubekræftet bruger. Triggeren udløses i det øjeblik, så den ventende række skal allerede findes.

Hvordan tjekker du din egen app?

Åbn din handle_new_user-funktion eller en hvilken som helst trigger på auth.users, og søg efter raw_user_meta_data. Navn og avatar derfra er i orden. Rolle, virksomhed, abonnement, tenant eller andet, der giver adgang, skal komme fra en tabel eller fra raw_app_meta_data, som brugeren ikke kan ændre.

Prøv det derefter: tilmeld dig via API’et med role_id eller role: 'admin' i options.data. Den nye konto skal ikke få noget ekstra.

Den slags tjek er første skridt i min metode til at teste RLS. For multi-tenant-apps, se også hvad RLS overser for en bruger i to orgs. De andre tjek, jeg kører på hver Supabase-app, står i min sikkerhedstjekliste til Lovable-apps. Det første, jeg kigger på i dashboardet, er hvilke schemas Data API’et eksponerer. Supabase sikkerhedstjek dækker roller, rettigheder, policies og auth-indstillinger mod 45 OWASP Top 10:2025-regler, ikke hvad en trigger kopierer fra tilmeldingsmetadata, så dette er stadig et manuelt tjek.

© 2026 Denys Zaichenko. Original artikel: https://madsalt.tech/da/notes/supabase-raw-user-meta-data-role/