---
title: "Supabase raw_user_meta_data: Registrierung wurde Owner"
description: "Ein handle_new_user-Trigger las role_id aus raw_user_meta_data, das der Browser setzt. Wie jeder Owner wurde, die Lösung per Einladung, ein Test."
url: "https://madsalt.tech/de/notes/supabase-raw-user-meta-data-role/"
author: "Denys Zaichenko"
language: "de"
published: 2026-10-18
---

# Supabase raw_user_meta_data: Registrierung wurde Owner

Kurz gesagt

`raw_user_meta_data` enthält, was der Browser an `supabase.auth.signUp()` übergeben hat. In einer Produktions-App las der `handle_new_user`-Trigger die Rolle des neuen Nutzers daraus, sodass sich jeder mit der Owner-Rollen-ID registrieren und das Konto übernehmen konnte. Die Lösung: Die Rolle kommt aus einer Tabelle, die nur der Server beschreibt, und sie wird gefüllt, bevor die Einladung verschickt wird.

Das war beim [Zeiterfassungs-Tool für eine Wirtschaftsprüfungsgesellschaft](https://madsalt.tech/de/work/audit-firm-time-tracking/), einer Supabase-App, in der jede Zugriffsregel in der Datenbank liegt. Der Fehler steckte im Trigger, der für jeden neuen Nutzer ein Profil anlegt.

## Was tat der Trigger?

Vereinfacht sah er so aus:

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

Der Einladungsablauf legte dort die richtige `role_id` ab, im normalen Betrieb funktionierte es also. Aber `raw_user_meta_data` sind die `options.data` einer Registrierung, und was darin steht, bestimmt der Browser:

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

Die Berechtigungsprüfung gab für Owner true zurück, bevor sie etwas anderes ansah, deshalb reichte dieses eine Feld für volle Kontrolle über das Konto. Die [Users-Anleitung](https://supabase.com/docs/guides/auth/users) von Supabase selbst sagt, dass Nutzer-Metadaten nicht in RLS-Policies oder Autorisierungslogik verwendet werden dürfen, weil der Nutzer sie ohne jede Prüfung ändern kann.

![Woher die Rolle eines neuen Nutzers kam. Der handle_new_user-Trigger vor und nach der Lösung. Vorher: jeder, der sich registriert: signUp options.data → raw_user_meta_data → handle_new_user → Owner-Rolle; Nachher: nur Eingeladene: Einladungsfunktion → pending_invites → handle_new_user → Eingeladenen-Rolle.](https://madsalt.tech/charts/de/supabase-raw-user-meta-data-role.svg)

*Aus dem Registrierungs-Trigger einer Supabase-App in Produktion, Juni 2026.*

## Was hat es behoben?

Die Rolle kommt jetzt aus einer privaten Tabelle, an die der Client nicht herankommt:

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

Die Einladungsfunktion läuft auf dem Server, nachdem die Berechtigung des Einladenden geprüft wurde. Sie schreibt zuerst die offene Einladung und erstellt erst dann den Einladungslink. Der Trigger sucht die Rolle über die E-Mail-Adresse und löscht die Zeile. Der Name kommt weiterhin aus den Metadaten, das ist in Ordnung: Es sind Anzeigedaten.

Die Reihenfolge ist wichtig. Eine Einladung legt den Nutzer sofort an: Die [Users-Anleitung](https://supabase.com/docs/guides/auth/users) von Supabase hält fest, dass das Einladen einer neuen E-Mail-Adresse einen unbestätigten Nutzer erzeugt. Der Trigger feuert in diesem Moment, die offene Einladung muss also schon existieren.

## Wie prüfen Sie Ihre eigene App?

Öffnen Sie Ihre `handle_new_user`-Funktion oder einen beliebigen Trigger auf `auth.users` und suchen Sie nach `raw_user_meta_data`. Name und Avatar von dort sind in Ordnung. Rolle, Firma, Tarif, Mandant oder alles andere, was Zugriff gewährt, muss aus einer Tabelle oder aus `raw_app_meta_data` kommen, das der Nutzer nicht ändern kann.

Probieren Sie es dann aus: Registrieren Sie sich über die API mit `role_id` oder `role: 'admin'` in `options.data`. Das neue Konto sollte nichts zusätzlich bekommen.

Diese Art von Prüfung ist der erste Schritt meiner [Methode, RLS zu testen](https://madsalt.tech/de/blog/how-to-test-supabase-rls/). Bei mandantenfähigen Apps lesen Sie auch, [was RLS bei einem Nutzer in zwei Organisationen übersieht](https://madsalt.tech/de/notes/supabase-rls-user-in-two-organizations/). Die anderen Prüfungen, die ich bei jeder Supabase-App durchführe, stehen in meiner [Sicherheits-Checkliste für Lovable-Apps](https://madsalt.tech/de/blog/lovable-app-security-checklist/). Die erste, die ich im Dashboard ansehe, ist [welche Schemas die Data API exponiert](https://madsalt.tech/de/notes/supabase-exposed-schemas/). Der [Supabase Sicherheitscheck](https://madsalt.tech/de/tools/supabase-security-check/) deckt Rollen, Grants, Policies und Auth-Einstellungen gegen 46 Regeln der OWASP Top 10:2025 ab, nicht aber, was ein Trigger aus den Metadaten der Registrierung übernimmt, daher bleibt dies eine manuelle Prüfung.
