Apoya mi contenido: 

Tabla de contenido

Cómo implementar roles y permisos en Supabase paso a paso

Supabase se apoya en PostgreSQL y su Row Level Security (RLS) para controlar el acceso a cada fila de tus tablas. En esta guía aprenderás, paso a paso, a diseñar roles, crear tablas de membresía, activar RLS, escribir policies seguras y cubrir casos comunes (CRUD por propietario, organizaciones, Storage y “admin global”).

1) Conceptos clave en 1 minuto

  • Roles de API: anon (usuarios no autenticados), authenticated (con sesión) y service_role (clave del servidor con permisos totales; nunca en el cliente).
  • RLS: al activarlo, todo queda denegado por defecto. Concedes acceso mediante políticas (USING para leer/actualizar/borrar y WITH CHECK para crear/actualizar).
  • Helpers JWT: en SQL puedes usar auth.uid() (UUID del usuario), auth.email() y auth.jwt() (claims JSON) para leer metadatos del token.

2) Modelo mínimo de datos (organizaciones y membresías)

Definimos una tabla de organizaciones y otra de membresías con un rol por miembro.

-- 2.1) Tipo enum para roles internos
create type public.org_role as enum ('owner','admin','member','viewer');

-- 2.2) Organizaciones
create table if not exists public.organizations (
  id uuid primary key default gen_random_uuid(),
  name text not null,
  created_at timestamptz not null default now()
);

-- 2.3) Membresías (usuario -> organización)
create table if not exists public.org_members (
  org_id uuid references public.organizations(id) on delete cascade,
  user_id uuid not null,         -- coincide con auth.users.id
  role public.org_role not null default 'member',
  created_at timestamptz not null default now(),
  primary key (org_id, user_id)
);

-- 2.4) Recurso ejemplo: proyectos bajo una organización
create table if not exists public.projects (
  id uuid primary key default gen_random_uuid(),
  org_id uuid not null references public.organizations(id) on delete cascade,
  title text not null,
  owner_id uuid not null,        -- dueño (normalmente creador)
  created_at timestamptz not null default now()
);

3) Activa RLS y crea funciones de ayuda

alter table public.organizations enable row level security;
alter table public.org_members  enable row level security;
alter table public.projects     enable row level security;

-- Helper: ¿el usuario actual tiene alguno de estos roles en la org?
create or replace function public.is_org_role(p_org uuid, roles text[])
returns boolean language sql stable as $$
  select exists(
    select 1
    from public.org_members m
    where m.org_id = p_org
      and m.user_id = auth.uid()
      and m.role::text = any(roles)
  );
$$;

-- Helper: ¿el usuario actual es miembro de la org?
create or replace function public.is_org_member(p_org uuid)
returns boolean language sql stable as $$
  select public.is_org_role(p_org, array['owner','admin','member','viewer']);
$$;

4) Políticas base por tabla

4.1) organizations

Solo miembros pueden verla; crear/editar solo “owner”/“admin”.

-- SELECT: visible solo para miembros
create policy orgs_select on public.organizations
for select
using ( public.is_org_member(id) );

-- INSERT: crear org si estás autenticado (te harás owner en un trigger o en la app)
create policy orgs_insert on public.organizations
for insert
with check ( auth.uid() is not null );

-- UPDATE/DELETE: solo roles elevados
create policy orgs_update on public.organizations
for update
using ( public.is_org_role(id, array['owner','admin']) );

create policy orgs_delete on public.organizations
for delete
using ( public.is_org_role(id, array['owner']) );

4.2) org_members

Lectura para miembros; gestionar miembros solo “owner”/“admin”.

-- SELECT: un miembro puede ver el listado de su org
create policy members_select on public.org_members
for select
using ( public.is_org_member(org_id) );

-- INSERT: añadir miembros si eres owner/admin
create policy members_insert on public.org_members
for insert
with check ( public.is_org_role(org_id, array['owner','admin']) );

-- UPDATE (cambiar rol) y DELETE (expulsar): owner/admin
create policy members_update on public.org_members
for update
using ( public.is_org_role(org_id, array['owner','admin']) );

create policy members_delete on public.org_members
for delete
using ( public.is_org_role(org_id, array['owner','admin']) );

4.3) projects

Lectura para miembros; crear proyectos si eres miembro; actualizar/eliminar si eres dueño o admin.

-- SELECT: cualquiera de la organización
create policy projects_select on public.projects
for select
using ( public.is_org_member(org_id) );

-- INSERT: cualquier miembro puede crear; asegúrate de setear owner_id = auth.uid()
create policy projects_insert on public.projects
for insert
with check (
  public.is_org_member(org_id)
  and owner_id = auth.uid()
);

-- UPDATE/DELETE: dueño o admin
create policy projects_update on public.projects
for update
using (
  owner_id = auth.uid() or public.is_org_role(org_id, array['owner','admin'])
);

create policy projects_delete on public.projects
for delete
using (
  owner_id = auth.uid() or public.is_org_role(org_id, array['owner','admin'])
);

5) Patrón “admin global” con claims personalizados

Si necesitas un superadmin que salte entre organizaciones, añade una claim en el JWT (p. ej. app_metadata.role = "superadmin") desde tu backend usando la clave service_role. Luego léela en las policies.

-- Política que permite todo si el JWT trae app_metadata.role = 'superadmin'
create policy orgs_superadmin_all on public.organizations
for all
using (
  coalesce( (auth.jwt() -> 'app_metadata' ->> 'role'), '' ) = 'superadmin'
)
with check (
  coalesce( (auth.jwt() -> 'app_metadata' ->> 'role'), '' ) = 'superadmin'
);

Nota: no intentes usar la claim role para “convertirte” en un rol de Postgres. Los roles de DB (anon/authenticated) los fija Supabase. Usa auth.jwt() para leer claims personalizadas dentro de las políticas.

6) Storage (archivos) con políticas

Los buckets de Storage también usan RLS. Ejemplo: bucket avatars, cada usuario solo ve y escribe lo suyo; admins de la organización pueden leer todo.

-- Crear bucket (Hazlo en el dashboard o via RPC)
-- Políticas:
create policy storage_read_own on storage.objects
for select
using (
  bucket_id = 'avatars'
  and (owner = auth.uid())  -- asumiendo que guardas owner (uuid) en metadata o path
);

create policy storage_upload_own on storage.objects
for insert
with check (
  bucket_id = 'avatars'
  and (owner = auth.uid())
);

-- Lectura por admin de la org (si almacenas org_id en metadata)
create policy storage_read_org_admin on storage.objects
for select
using (
  bucket_id = 'avatars'
  and public.is_org_role( (metadata ->> 'org_id')::uuid, array['owner','admin'] )
);

7) Triggers útiles

Cuando crees una organización, suele convenir asignar automáticamente al creador como owner.

create or replace function public.add_owner_after_org_insert()
returns trigger language plpgsql as $$
begin
  insert into public.org_members(org_id, user_id, role)
  values (new.id, auth.uid(), 'owner');
  return new;
end; $$;

drop trigger if exists t_org_owner on public.organizations;
create trigger t_org_owner
after insert on public.organizations
for each row execute procedure public.add_owner_after_org_insert();

8) Uso desde React / React Native

En el cliente, no necesitas lógica extra para permisos: las políticas RLS protegen el acceso. Solo envía el JWT del usuario.

// Ejemplo con supabase-js
import { createClient } from '@supabase/supabase-js';
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);

// Seleccionar proyectos visibles para el usuario actual
const { data, error } = await supabase
  .from('projects')
  .select('*')
  .eq('org_id', selectedOrgId);

// Insert (owner_id se fija al usuario actual)
const { error: errInsert } = await supabase
  .from('projects')
  .insert({ org_id: selectedOrgId, title: 'Nuevo', owner_id: (await supabase.auth.getUser()).data.user.id });

9) Pruebas y depuración

  • Usa el Policy Debugger del dashboard: ejecuta acciones como “anon”, “authenticated” o con un usuario concreto.
  • Recuerda: con RLS activo, cualquier operación sin policy aplicable será denegada.
  • Evita políticas demasiado permisivas como using (true) salvo casos públicos muy justificados.

10) Errores comunes (y cómo evitarlos)

  • Olvidar WITH CHECK: permite que cualquiera inserte filas que luego no podrá ver. Especifica siempre la condición para insert/update.
  • Confiar lógica sensible al cliente: las decisiones de permiso deben vivir en las policies, no en el frontend.
  • No guardar referencias: para políticas por organización, guarda siempre org_id (y opcionalmente owner_id).
  • Usar service_role en el cliente: jamás. Resérvalo para backends/Edge Functions.

Conclusión

Con RLS y policies bien diseñadas, Supabase te permite implementar roles y permisos robustos sin servidores intermedios. Parte de un modelo claro (membresías y roles), activa RLS desde el día 1 y construye políticas pequeñas, específicas y testeables. Así obtendrás seguridad por defecto y un backend listo para escalar.

#Supabase #RLS #Permisos #Roles #PostgreSQL #Seguridad #BackendAsAService #React #ReactNative #JavaScript

¡Comunícate con nosotros!

Ads Blocker Image Powered by Code Help Pro

Bloqueador de anuncios detectado!!!

 Por favor, apóyanos desactivando este bloqueador de anuncios para seguir creando contenido que te gusta 🙏🏼

Powered By
Best Wordpress Adblock Detecting Plugin | CHP Adblock