---
title: "Supabase raw_user_meta_data: un signup diventato Owner"
description: "Il trigger handle_new_user leggeva role_id da raw_user_meta_data, impostato dal browser al signup. Come chiunque diventava Owner e la correzione con inviti."
url: "https://madsalt.tech/it/notes/supabase-raw-user-meta-data-role/"
author: "Denys Zaichenko"
language: "it"
published: 2026-10-18
---

# Supabase raw_user_meta_data: un signup diventato Owner

In breve

`raw_user_meta_data` contiene tutto ciò che il browser ha passato a `supabase.auth.signUp()`. In un'app in produzione il trigger `handle_new_user` ne leggeva il ruolo del nuovo utente, quindi chiunque poteva registrarsi con l'id del ruolo Owner e diventare proprietario dell'account. La correzione: il ruolo arriva da una tabella in cui scrive solo il server, compilata prima dell'invio dell'invito.

Succedeva nel [time tracker della società di revisione](https://madsalt.tech/it/work/audit-firm-time-tracking/), un'app Supabase in cui ogni regola di accesso vive nel database. Il bug stava nel trigger che crea un profilo per ogni nuovo utente.

## Cosa faceva il trigger?

In versione semplificata, era così:

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

Il flusso di invito metteva lì il `role_id` giusto, quindi nell'uso normale funzionava. Ma `raw_user_meta_data` è l'`options.data` di un signup, e a decidere cosa ci va è il browser:

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

Il controllo dei permessi restituiva true per gli owner prima di guardare qualsiasi altra cosa, quindi quel solo campo dava il controllo completo dell'account. La stessa [guida Users](https://supabase.com/docs/guides/auth/users) di Supabase dice che i metadati utente non vanno usati nelle policy RLS né nella logica di autorizzazione, perché l'utente può modificarli senza alcun controllo.

![Da dove arrivava il ruolo di un nuovo utente. Il trigger handle_new_user prima e dopo la correzione. Prima: chiunque si registri: signUp options.data → raw_user_meta_data → handle_new_user → Ruolo Owner; Dopo: solo chi è stato invitato: Funzione di invito → pending_invites → handle_new_user → Ruolo dell'invito.](https://madsalt.tech/charts/it/supabase-raw-user-meta-data-role.svg)

*Dal trigger di signup di un'app Supabase in produzione, giugno 2026.*

## Cosa l'ha risolto?

Ora il ruolo arriva da una tabella privata che il client non può toccare:

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

La funzione di invito gira sul server, con il permesso di chi invita già verificato. Scrive prima l'invito in sospeso e solo dopo crea il link di invito. Il trigger cerca il ruolo per email ed elimina la riga. Il nome continua a venire dai metadati, e va bene: sono dati di visualizzazione.

L'ordine conta. Un invito crea subito l'utente: la [guida Users](https://supabase.com/docs/guides/auth/users) di Supabase precisa che invitare una nuova email crea un utente non confermato. Il trigger scatta in quel momento, quindi la riga in sospeso deve già esistere.

## Come controllare la sua app?

Apra la sua funzione `handle_new_user`, o qualsiasi trigger su `auth.users`, e cerchi `raw_user_meta_data`. Nome e avatar presi da lì vanno bene. Ruolo, azienda, piano, tenant o qualsiasi altra cosa che dia accesso deve venire da una tabella o da `raw_app_meta_data`, che l'utente non può modificare.

Poi provi: si registri tramite l'API con `role_id` o `role: 'admin'` in `options.data`. Il nuovo account non deve ottenere nulla in più.

Questo tipo di controllo è il primo passo del mio [metodo per testare la RLS](https://madsalt.tech/it/blog/how-to-test-supabase-rls/). Per le app multi-tenant, veda anche [cosa la RLS non vede per un utente in due org](https://madsalt.tech/it/notes/supabase-rls-user-in-two-organizations/). Gli altri controlli che eseguo su ogni app Supabase sono nella mia [checklist di sicurezza per le app Lovable](https://madsalt.tech/it/blog/lovable-app-security-checklist/). Il primo che guardo nella dashboard è [quali schemi espone la Data API](https://madsalt.tech/it/notes/supabase-exposed-schemas/). Il [Controllo sicurezza Supabase](https://madsalt.tech/it/tools/supabase-security-check/) copre ruoli, grant, policy e impostazioni di autenticazione con 46 regole OWASP Top 10:2025, non ciò che un trigger copia dai metadati di registrazione, quindi questo resta un controllo manuale.
