---
title: "Supabase raw_user_meta_data: så blev en registrering Owner"
description: "Triggern handle_new_user tog role_id från raw_user_meta_data, som webbläsaren sätter vid registrering. Hur vem som helst blev Owner, och åtgärden."
url: "https://madsalt.tech/sv/notes/supabase-raw-user-meta-data-role/"
author: "Denys Zaichenko"
language: "sv"
published: 2026-10-18
---

# Supabase raw_user_meta_data: så blev en registrering Owner

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](https://madsalt.tech/sv/work/audit-firm-time-tracking/), 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:

```sql
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:

```js
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](https://supabase.com/docs/guides/auth/users) 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.](https://madsalt.tech/charts/sv/supabase-raw-user-meta-data-role.svg)

*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:

```sql
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](https://supabase.com/docs/guides/auth/users) 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](https://madsalt.tech/sv/blog/how-to-test-supabase-rls/). För multi-tenant-appar, se också [vad RLS missar för en användare i två organisationer](https://madsalt.tech/sv/notes/supabase-rls-user-in-two-organizations/). De andra kontrollerna jag kör på varje Supabase-app finns i min [säkerhetschecklista för Lovable-appar](https://madsalt.tech/sv/blog/lovable-app-security-checklist/). Det första jag tittar på i dashboarden är [vilka scheman Data API:et exponerar](https://madsalt.tech/sv/notes/supabase-exposed-schemas/). [Supabase säkerhetskontroll](https://madsalt.tech/sv/tools/supabase-security-check/) täcker roller, behörigheter, policyer och auth-inställningar mot 46 OWASP Top 10:2025-regler, inte vad en trigger kopierar från registreringsmetadata, så det här är fortfarande en manuell kontroll.
