Saltar a contenido

Capítulo 31 — Firestore Security Rules: Protegiendo tu base de datos

Recursos visuales propuestos

Antes de desarrollar este capítulo conviene separar los recursos que explican ideas de seguridad de forma pedagógica de aquellos que describen el flujo real de autorización dentro de Firebase. El flujo de autorización, la comparación entre acceso permitido y denegado, los ejemplos de reglas, la organización del archivo firestore.rules y los casos de uso deben presentarse como imágenes didácticas, porque ayudan a construir intuición visual sobre decisiones de seguridad y patrones comunes. En cambio, el flujo Cliente → Firestore → Security Rules → Base de datos, la arquitectura completa de autorización, la evaluación interna de una regla y la integración entre Authentication, Custom Claims y Firestore Rules deben representarse como diagramas SVG, ya que implican componentes múltiples, orden de evaluación, fuentes de identidad y relaciones técnicas entre capas del sistema.[cite:1][cite:2][cite:3]

Imágenes didácticas

  1. Flujo de autorización. Conviene como imagen didáctica porque permite explicar de forma introductoria cómo una petición puede ser aprobada o rechazada antes de tocar los datos.[cite:1]
  2. Comparación entre acceso permitido y denegado. Debe ser imagen didáctica porque el valor pedagógico está en el contraste rápido de escenarios.
  3. Ejemplos de reglas. Funciona mejor como imagen didáctica cuando se presentan patrones cortos y comunes, como acceso del propietario, lectura pública o escritura autenticada.[cite:2]
  4. Organización del archivo firestore.rules. Debe ser imagen didáctica porque ayuda a visualizar estructura, módulos y funciones sin requerir un modelo espacial complejo.
  5. Casos de uso. Deben presentarse como imágenes didácticas para resumir plataformas educativas, paneles administrativos y escenarios multiempresa.

Diagramas SVG

  1. Flujo Cliente → Firestore → Security Rules → Base de datos. Debe ser SVG porque representa claramente el pipeline de evaluación previa al acceso a datos.[cite:1]
  2. Arquitectura completa de autorización. Requiere SVG porque integra clientes, Authentication, reglas, documentos de soporte, claims y base de datos.
  3. Evaluación interna de una regla. Debe ser SVG porque necesita mostrar request, resource, request.resource, auth, time, path, funciones auxiliares y resultado booleano.[cite:2][cite:4]
  4. Integración entre Authentication, Custom Claims y Firestore Rules. Debe ser SVG porque involucra identidad, token, autorización y datos de negocio en un mismo modelo técnico.[cite:2][cite:4]

Objetivos de aprendizaje

Al finalizar este capítulo, el lector será capaz de:

  • Comprender qué son las Firestore Security Rules y por qué son el núcleo de la autorización en aplicaciones cliente que acceden directamente a Firestore.[cite:1]
  • Diferenciar con precisión autenticación y autorización, y entender cómo se relacionan mediante request.auth y los claims del token.[cite:2][cite:4]
  • Diseñar reglas seguras con Rules Version 2, organizadas por rutas, funciones reutilizables y principios de mínimo privilegio.[cite:1][cite:2]
  • Entender cómo Firestore evalúa lecturas, escrituras, consultas y accesos a documentos auxiliares mediante get(), exists() y getAfter().[cite:2]
  • Construir reglas profesionales para el proyecto oficial del libro, protegiendo usuarios, instituciones, docentes, alumnos, grupos, tareas, evidencias, credenciales, archivos y configuraciones.
  • Optimizar reglas para mantener seguridad sin degradar rendimiento, evitar costos innecesarios y preparar la base de datos para producción.[cite:2][cite:3]

Introducción

Cuando una aplicación usa Firestore desde clientes web o móviles, la base de datos deja de estar protegida por un backend tradicional que decide quién puede leer o escribir. En Firebase, ese papel lo cumplen las Security Rules. Son el sistema que autoriza o rechaza cada petición del cliente antes de que la base de datos entregue o acepte datos.[cite:1]

Este punto es crítico: no basta con que el usuario esté autenticado. Un usuario autenticado solo ha demostrado quién es. Aún falta responder qué documentos puede consultar, cuáles puede modificar, qué campos puede cambiar, en qué condiciones y bajo qué límites. Esa segunda capa es la autorización, y ahí viven las Firestore Security Rules.[cite:1][cite:2]

La documentación oficial describe las reglas como un mecanismo de control de acceso y validación de datos, escrito en un formato expresivo que permite construir sistemas basados en usuario y en roles junto con Firebase Authentication.[cite:1] Eso significa que las reglas no son un detalle opcional ni un “extra” de seguridad: son parte integral del diseño de la aplicación.

Además, una regla mal escrita no solo pone en riesgo la información. También puede romper consultas legítimas, disparar lecturas auxiliares innecesarias, generar errores de permisos difíciles de diagnosticar o producir una falsa sensación de protección. Por eso este capítulo no se limitará a mostrar ejemplos aislados; explicará cómo piensa el motor de reglas, cómo evaluar decisiones de diseño, cómo organizar un archivo firestore.rules profesional y cómo aplicar un modelo de autorización real al proyecto transversal del libro.

Desarrollo completo

Introducción conceptual

¿Qué son las Firestore Security Rules?

Las Firestore Security Rules son un conjunto declarativo de reglas que Firebase evalúa antes de permitir una lectura o escritura desde las bibliotecas cliente de Firestore. La documentación oficial indica que cada solicitud de cliente web o móvil se evalúa contra las reglas antes de leer o escribir cualquier dato, y si las reglas niegan el acceso a cualquiera de las rutas solicitadas, la petición completa falla.[cite:1]

Su papel no es solo bloquear accesos. También validan estructura y consistencia de datos, permitiendo imponer restricciones sobre campos, tipos, relaciones y cambios permitidos.[cite:2]

¿Por qué son necesarias?

Porque en el modelo Firebase el cliente puede hablar directamente con Firestore. Si la base de datos aceptara toda petición autenticada sin reglas específicas, cualquier usuario podría intentar leer o modificar datos ajenos. Y si se configurara una regla permisiva global como allow read, write: if true;, se estaría abriendo toda la base de datos a cualquiera, algo que la propia documentación advierte explícitamente que nunca debe usarse en producción.[cite:1]

Diferencia entre autenticación y autorización

  • Autenticación: responde quién es el usuario.
  • Autorización: responde qué puede hacer ese usuario.

En reglas, la autenticación aparece mediante request.auth, que contiene el contexto de autenticación del cliente y, cuando existe, el uid y un mapa de claims del token.[cite:2][cite:4] La autorización ocurre cuando las reglas toman ese contexto y lo comparan con el documento, la ruta, la consulta o datos auxiliares.

¿Qué ocurre cuando una regla falla?

La documentación señala que, si las reglas niegan acceso a una ruta solicitada, la solicitud completa falla.[cite:1] En la práctica, el cliente recibe un error de permisos denegados. No hay un “acceso parcial” ni una consulta que devuelva solo lo permitido. Esto es importante porque muchas veces se intenta usar reglas como si fueran filtros, y Firestore no funciona así.[cite:2][cite:3]

Funcionamiento interno

Motor de evaluación

El motor de reglas evalúa una condición booleana. Si el resultado es true, la operación se permite; si es false, se deniega.[cite:2] Toda regla se reduce finalmente a esa pregunta: ¿la condición completa es verdadera o no?

Sin embargo, llegar a ese true o false puede implicar varias piezas:

  • autenticación del usuario;
  • ruta solicitada;
  • estado actual del documento;
  • estado futuro del documento en una escritura;
  • tiempo de la petición;
  • restricciones de la consulta;
  • lecturas auxiliares a otros documentos;
  • funciones personalizadas.

Flujo de autorización

El flujo conceptual es el siguiente:

  1. El cliente envía una lectura o escritura.
  2. Firestore identifica la ruta y el tipo de operación.
  3. Busca los bloques match aplicables.
  4. Evalúa los allow que correspondan a esa operación.
  5. Resuelve funciones y expresiones usadas en la condición.
  6. Si una condición autorizadora resulta verdadera, la operación se permite; en caso contrario, se deniega.

Este flujo ocurre antes de tocar la base de datos como respuesta final al cliente.[cite:1]

request

La referencia oficial de reglas define Request como la representación de la solicitud. Sus propiedades incluyen auth, method, path, query, resource y time.[cite:4]

request es, por tanto, la fotografía de la operación entrante.

resource

La documentación de condiciones explica que resource se refiere al documento solicitado y resource.data es el mapa de campos y valores almacenados actualmente.[cite:2] En lecturas, es el documento existente. En escrituras de actualización y borrado, representa el estado actual antes del cambio.

request.resource

La documentación oficial indica que, cuando un ruleset permite una escritura pendiente, request.resource contiene el estado futuro del documento. En update, incluso si solo cambian algunos campos, request.resource contiene el documento resultante después de aplicar la actualización.[cite:2]

Esta diferencia es crucial:

  • resource.data = estado actual.
  • request.resource.data = estado propuesto.

Compararlos es la base de muchas validaciones seguras.

auth

request.auth contiene el contexto de autenticación. La referencia oficial señala que incluye uid y token, donde token es un mapa de claims JWT como email, email_verified, firebase.sign_in_provider, firebase.tenant y cualquier claim personalizado que se agregue al token.[cite:4]

time

La propiedad request.time es el momento en que la petición fue recibida por el servicio. La referencia añade que, para escrituras que incluyen timestamps generados por el servidor, este tiempo coincide con el server timestamp.[cite:4] Esto permite reglas temporales o validación de campos que deben ser sellados por servidor.

path

request.path es la ruta del recurso afectado.[cite:4] No es la herramienta más usada en reglas cotidianas, pero forma parte del contexto evaluable y ayuda a comprender que la autorización no depende solo del contenido del documento, sino también de dónde vive ese documento en la jerarquía.

Operaciones

La referencia de Request enumera los métodos de petición posibles como get, list, create, update y delete.[cite:4] Además, la sintaxis de reglas permite atajos como read y write, que agrupan operaciones.

read

Agrupa lectura de documento individual y consultas.

get

Representa recuperación de un documento específico. Es útil separarlo de list cuando se quieren aplicar condiciones distintas a una lectura puntual y a una consulta.[cite:3][cite:4]

list

Representa consultas o lecturas de múltiples documentos. La documentación muestra cómo request.query puede usarse para restringir propiedades como limit, offset y orderBy.[cite:3][cite:4]

create

Se aplica cuando el documento aún no existe y se intenta crearlo.

update

Se aplica cuando el documento ya existe y se modifica.

delete

Se aplica cuando se solicita eliminar un documento.

write

Agrupa create, update y delete. Es cómodo para reglas sencillas, pero en sistemas reales suele ser mejor separar operaciones porque crear no es lo mismo que modificar y modificar no es lo mismo que borrar.

Sintaxis

match

La documentación oficial indica que todas las reglas consisten en sentencias match, que identifican documentos en la base de datos, y expresiones allow, que controlan acceso a esos documentos.[cite:1]

Ejemplo básico:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow read: if request.auth != null;
    }
  }
}

allow

allow vincula una o varias operaciones con una condición booleana.

allow read, write: if request.auth != null;

function

La documentación de condiciones explica que se pueden definir funciones reutilizables con function, pero con limitaciones importantes: una sola sentencia return, sin bucles ni llamadas a servicios externos, sin recursión y con profundidad total de pila limitada a 10. En Rules v2 también se pueden usar variables locales con let y hasta 10 bindings por función.[cite:2]

Variables

Las variables de wildcard definidas en match, como {userId}, quedan disponibles dentro del bloque de reglas.[cite:2] También pueden construirse rutas para get() o exists() usando la sintaxis $(variable) al interpolarlas en paths completos.[cite:2]

Reutilización de reglas

A medida que crece el ruleset, la reutilización es indispensable. La documentación recomienda usar funciones para mantener las reglas más mantenibles cuando aumenta su complejidad.[cite:2]

Validaciones

Esta es la zona donde una aplicación deja de tener reglas superficiales y empieza a tener reglas de producción.

Usuario autenticado

El patrón más básico es exigir autenticación:

allow read, write: if request.auth != null;

Firebase documenta este patrón como uno de los más comunes para controlar acceso basado en estado de autenticación.[cite:2]

Propietario del documento

Otro patrón clásico consiste en permitir acceso solo si el UID del usuario coincide con el identificador o con un campo del documento. La documentación oficial lo ilustra con documentos users/{userId}, donde request.auth.uid == userId protege lectura, actualización y borrado.[cite:2]

Roles

Para modelos más avanzados, las reglas pueden apoyarse en claims del token o en documentos auxiliares. Un sistema institucional rara vez puede resolverse solo con “propietario sí/no”. Se necesitan roles como:

  • superadmin;
  • admin institucional;
  • docente;
  • alumno;
  • personal operativo.

Campos obligatorios

Las reglas pueden inspeccionar request.resource.data y exigir la presencia de campos críticos. Aunque el lenguaje no es un sistema de tipos completo, sí permite construir validaciones útiles.

Tipos de datos

Las reglas pueden validar forma y coherencia de datos. No sustituyen toda validación de backend, pero sí son la última línea de defensa para impedir que un cliente escriba estructuras peligrosas o inconsistentes.

Restricción de modificaciones

La documentación da un ejemplo claro: permitir actualización solo si population > 0 y además impedir cambiar el campo name, comparando request.resource.data.name con resource.data.name.[cite:2] Ese patrón es muy poderoso: reglas que no solo autorizan, sino que limitan qué puede cambiar.

Restricción por fechas

request.time permite construir reglas temporales. Por ejemplo, permitir que una tarea se entregue solo antes de cierta fecha almacenada o exigir que un campo de timestamp haya sido sellado por servidor.[cite:4]

Restricción por institución

En el proyecto oficial del libro, este criterio será esencial. Un docente o alumno debe operar dentro de su institución. La autorización no debe depender solo del rol, sino también del contexto institucional.

Integración

Firebase Authentication

Firebase indica que, para construir sistemas de acceso basados en usuarios o roles, se debe usar Firebase Authentication junto con Firestore Security Rules.[cite:1] La identidad del usuario entra al motor de reglas mediante request.auth.[cite:2][cite:4]

Custom Claims

Los claims personalizados viven en request.auth.token. La referencia de Request explica que token es un mapa de claims JWT.[cite:4] Esto permite marcar usuarios con propiedades como role, institutionId o isSuperAdmin sin tener que leer siempre un documento adicional.

Firestore

Las reglas no viven separadas de la estructura de datos. Diseñar colecciones, subcolecciones y documentos también es diseñar autorización. Si un modelo de datos no facilita saber de quién es un documento, a qué institución pertenece o qué rol debe acceder, las reglas se vuelven más costosas y complejas.

Cloud Functions

Cloud Functions no reemplaza las reglas cuando el acceso viene desde clientes. Pero sí puede complementar el modelo de seguridad: por ejemplo, una función admin puede establecer custom claims, crear documentos normalizados o ejecutar escrituras privilegiadas desde Admin SDK cuando cierta operación no debe exponerse al cliente.

Optimización

Rendimiento

El rendimiento de reglas no es un asunto teórico. La documentación establece límites de llamadas de acceso a documentos por evaluación: 10 para solicitudes de documento único y queries; 20 para lecturas multi-documento, transacciones y batches, además del límite previo de 10 por operación.[cite:2] Excederlos produce permission denied.[cite:2]

Costos

La documentación también advierte que el uso de get() y exists() ejecuta operaciones de lectura facturables incluso si las reglas terminan rechazando la petición.[cite:2] Esto significa que un diseño de reglas puede impactar costos de manera real.

Reglas reutilizables

Reutilizar reglas con funciones no solo mejora mantenibilidad; también reduce errores de consistencia entre colecciones y operaciones.[cite:2]

Organización del archivo

Un archivo firestore.rules profesional debe estar estructurado, comentado con mesura y organizado por dominios o módulos. No conviene un bloque gigantesco de cientos de líneas sin separación conceptual.

Pruebas

Firebase proporciona un simulador de reglas en la consola para probar lecturas, escrituras y eliminaciones autenticadas o no autenticadas contra el ruleset del editor.[cite:1] Además, el uso de CLI permite versionar reglas como parte del proyecto y desplegarlas junto al código.[cite:1]

Ejemplos paso a paso

Ejemplo 1: Regla mínima segura por usuario

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow read, update, delete: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null && request.auth.uid == userId;
    }
  }
}

Qué ocurre internamente

  1. El cliente intenta acceder a /users/abc123.
  2. Firestore evalúa el bloque match correspondiente.
  3. Comprueba si hay autenticación (request.auth != null).[cite:2]
  4. Compara request.auth.uid con el wildcard {userId}.[cite:2]
  5. Si ambos coinciden, autoriza; si no, deniega.

Ejemplo 2: Evitar cambios no permitidos

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /profiles/{userId} {
      allow update: if request.auth != null
        && request.auth.uid == userId
        && request.resource.data.institutionId == resource.data.institutionId
        && request.resource.data.role == resource.data.role;
    }
  }
}

Aquí se permite al usuario actualizar su perfil, pero no cambiar institución ni rol. La seguridad no está en “puede actualizar”, sino en “puede actualizar solo ciertas partes”.

Ejemplo 3: Acceso basado en documento auxiliar

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    function isAdmin() {
      return request.auth != null &&
        get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true;
    }

    match /institutions/{institutionId} {
      allow delete: if isAdmin();
    }
  }
}

La documentación muestra este patrón al usar get() para revisar si el documento del usuario tiene un campo admin igual a true.[cite:2]

Ejemplo 4: Controlar consultas con request.query

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyId} {
      function authorOrPublished() {
        return resource.data.published == true || request.auth.uid == resource.data.author;
      }

      allow list: if request.query.limit <= 10 && authorOrPublished();
      allow get: if authorOrPublished();
    }
  }
}

Este patrón está documentado por Firebase para denegar queries sin límite o con límite excesivo, además de separar get y list.[cite:3]

Ejemplo 5: Consultas y por qué las reglas no son filtros

La documentación insiste en que las reglas no son filtros: una query es todo o nada y Firestore evalúa su conjunto potencial de resultados, no los documentos reales ya existentes.[cite:2][cite:3]

Ejemplo de regla:

match /cities/{city} {
  allow read: if resource.data.visibility == 'public';
}

Consulta inválida:

db.collection("cities").get();

Consulta válida:

db.collection("cities").where("visibility", "==", "public").get();

La segunda funciona porque la query garantiza que todos los resultados potenciales cumplirán la condición de la regla.[cite:2]

Proyecto transversal: reglas completas del sistema

A continuación se propone una arquitectura de autorización para el proyecto oficial del libro, una plataforma educativa con credenciales digitales y gestión documental.

Roles del sistema

  • superadmin: administra múltiples instituciones.
  • institutionAdmin: administra recursos de una institución específica.
  • teacher: gestiona grupos, tareas y evidencias dentro de su institución.
  • student: accede a sus datos, grupos, tareas, evidencias y credenciales.
  • staff: rol operativo con permisos limitados.

Principios del modelo

  1. Toda operación exige autenticación salvo recursos explícitamente públicos.
  2. El acceso se restringe por institución.
  3. Los claims sirven para autorización rápida global.
  4. Los documentos contienen campos mínimos que facilitan autorización segura: institutionId, ownerId, teacherId, studentId, visibility, role.
  5. Las reglas no sustituyen la lógica privilegiada de backend; la complementan.

Estructura propuesta de firestore.rules

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {

    function isSignedIn() {
      return request.auth != null;
    }

    function uid() {
      return request.auth.uid;
    }

    function hasRole(role) {
      return isSignedIn() && request.auth.token.role == role;
    }

    function isSuperAdmin() {
      return isSignedIn() && request.auth.token.superadmin == true;
    }

    function sameInstitution(institutionId) {
      return isSignedIn() && request.auth.token.institutionId == institutionId;
    }

    function isInstitutionAdmin(institutionId) {
      return sameInstitution(institutionId) && request.auth.token.role == 'institutionAdmin';
    }

    function isTeacher(institutionId) {
      return sameInstitution(institutionId) && request.auth.token.role == 'teacher';
    }

    function isStudent(institutionId) {
      return sameInstitution(institutionId) && request.auth.token.role == 'student';
    }

    function ownsDocument(ownerId) {
      return isSignedIn() && uid() == ownerId;
    }

A partir de esta base, pueden definirse bloques por colección.

Usuarios

    match /users/{userId} {
      allow get: if isSignedIn() && (uid() == userId || isSuperAdmin());
      allow list: if isSuperAdmin();
      allow create: if isSignedIn() && uid() == userId;
      allow update: if isSignedIn() && (
        uid() == userId || isSuperAdmin()
      )
      && request.resource.data.role == resource.data.role
      && request.resource.data.institutionId == resource.data.institutionId;
      allow delete: if isSuperAdmin();
    }

Justificación:

  • el usuario puede crear su documento inicial;
  • no puede escalar su rol ni cambiar de institución desde cliente;
  • la lista completa de usuarios es demasiado sensible para usuarios comunes.

Instituciones

    match /institutions/{institutionId} {
      allow get: if sameInstitution(institutionId) || isSuperAdmin();
      allow list: if isSuperAdmin();
      allow create, update: if isSuperAdmin();
      allow delete: if false;
    }

Justificación: las instituciones son entidades maestras. Su gestión se reserva al nivel más alto.

Docentes y alumnos

    match /teachers/{teacherId} {
      allow get: if isSignedIn() && (
        uid() == teacherId ||
        sameInstitution(resource.data.institutionId)
      );
      allow list: if isSignedIn() && sameInstitution(request.auth.token.institutionId);
      allow create, update: if isInstitutionAdmin(request.resource.data.institutionId) || isSuperAdmin();
      allow delete: if isSuperAdmin();
    }

    match /students/{studentId} {
      allow get: if isSignedIn() && (
        uid() == studentId ||
        sameInstitution(resource.data.institutionId)
      );
      allow list: if isInstitutionAdmin(request.auth.token.institutionId) || isSuperAdmin();
      allow create, update: if isInstitutionAdmin(request.resource.data.institutionId) || isSuperAdmin();
      allow delete: if isSuperAdmin();
    }

En producción real, convendría refinar aún más los criterios de lectura por rol y tipo de dato expuesto.

Grupos

    match /groups/{groupId} {
      allow get: if isSignedIn() && sameInstitution(resource.data.institutionId);
      allow list: if isSignedIn() && sameInstitution(request.auth.token.institutionId);
      allow create: if isTeacher(request.resource.data.institutionId) || isInstitutionAdmin(request.resource.data.institutionId);
      allow update: if sameInstitution(resource.data.institutionId)
        && (isInstitutionAdmin(resource.data.institutionId) || uid() == resource.data.teacherId);
      allow delete: if isInstitutionAdmin(resource.data.institutionId) || isSuperAdmin();
    }

Tareas

    match /tasks/{taskId} {
      allow get: if isSignedIn() && sameInstitution(resource.data.institutionId);
      allow list: if isSignedIn() && sameInstitution(request.auth.token.institutionId);
      allow create: if isTeacher(request.resource.data.institutionId);
      allow update, delete: if isTeacher(resource.data.institutionId) && uid() == resource.data.teacherId;
    }

Evidencias

    match /evidences/{evidenceId} {
      allow get: if isSignedIn() && (
        uid() == resource.data.studentId ||
        uid() == resource.data.teacherId ||
        isInstitutionAdmin(resource.data.institutionId)
      );
      allow list: if isSignedIn() && sameInstitution(request.auth.token.institutionId);
      allow create: if isStudent(request.resource.data.institutionId)
        && uid() == request.resource.data.studentId;
      allow update: if uid() == resource.data.studentId
        && request.resource.data.studentId == resource.data.studentId
        && request.resource.data.teacherId == resource.data.teacherId;
      allow delete: if isInstitutionAdmin(resource.data.institutionId) || isSuperAdmin();
    }

Credenciales digitales

    match /credentials/{credentialId} {
      allow get: if isSignedIn() && (
        uid() == resource.data.ownerId ||
        isInstitutionAdmin(resource.data.institutionId) ||
        isSuperAdmin()
      );
      allow list: if isInstitutionAdmin(request.auth.token.institutionId) || isSuperAdmin();
      allow create, update: if false;
      allow delete: if isSuperAdmin();
    }

Justificación: la emisión y modificación de credenciales digitales debería realizarse desde Cloud Functions o backend privilegiado, no directamente desde cliente.

Archivos y metadatos

Si los archivos viven en Cloud Storage, Firestore puede guardar metadatos. Esos metadatos deben protegerse al menos con los mismos criterios del recurso documental asociado.

Configuraciones

    match /settings/{settingId} {
      allow get: if isInstitutionAdmin(resource.data.institutionId) || isSuperAdmin();
      allow list: if false;
      allow create, update: if isInstitutionAdmin(request.resource.data.institutionId) || isSuperAdmin();
      allow delete: if isSuperAdmin();
    }
  }
}

Casos reales

Plataforma educativa

Una plataforma educativa combina distintos actores y niveles de sensibilidad. Un alumno no debe ver expedientes completos de otros alumnos, un docente no debería modificar configuraciones institucionales globales y un administrador institucional no necesariamente debe operar fuera de su propia institución. Las reglas deben reflejar ese reparto real de poder.

Sistema multiempresa

La documentación de Firestore muestra la importancia de duplicar en el documento ciertos campos que facilitan proteger consultas por usuario o por ruta, especialmente en collection group queries.[cite:3] Esa misma idea aplica a SaaS multiempresa: conviene tener tenantId o institutionId en cada documento sensible para que la autorización sea viable y eficiente.

Panel administrativo

Los paneles administrativos requieren atención especial porque suelen necesitar lecturas más amplias. No se debe resolver esto con allow read: if request.auth != null;. Debe definirse quién puede listar, qué puede listar y bajo qué filtros o límites.

Expedientes digitales

Los expedientes combinan datos personales, documentos y trazabilidad. Son candidatos claros para reglas basadas en propietario, institución y rol operativo.

Credenciales digitales

Las credenciales digitales representan un activo de alta confianza. Por eso conviene que su creación y actualización estén mediadas por Cloud Functions y que el cliente solo lea lo que le corresponde.

Comparaciones técnicas

Autenticado vs autorizado

Criterio Usuario autenticado Usuario autorizado
Identidad conocida Sí [cite:2][cite:4] Sí [cite:2][cite:4]
Puede acceder a cualquier documento No Solo si la regla lo permite [cite:1][cite:2]
Garantiza seguridad por sí solo No Tampoco, si la regla está mal diseñada
Uso en reglas request.auth != null [cite:2] condiciones adicionales sobre documento, claims o ruta [cite:2][cite:4]

resource vs request.resource

Variable Significado Uso típico
resource.data Estado actual del documento [cite:2] validar lectura, comparar cambios, autorizar updates
request.resource.data Estado futuro propuesto del documento [cite:2][cite:4] impedir cambios indebidos, validar campos al crear o actualizar

get vs list

Operación Alcance Motivo para separarla
get documento único [cite:4] permitir lectura puntual sin abrir consultas amplias
list consultas y lecturas múltiples [cite:3][cite:4] controlar limit, orderBy y restricciones de query

Documento auxiliar vs Custom Claims

Criterio Documento auxiliar en Firestore Custom Claims
Flexibilidad Alta Alta para atributos de identidad
Costo en reglas Puede implicar lecturas facturables con get() [cite:2] No requiere lectura adicional del documento
Actualización Más directa en datos Requiere actualizar token/claims
Ideal para membresías, relaciones complejas roles globales o privilegios estables

Buenas prácticas

  • Aplicar el principio de mínimo privilegio: otorgar solo el acceso estrictamente necesario.
  • Separar get y list cuando el caso lo amerite.[cite:3][cite:4]
  • Diseñar el modelo de datos pensando en la autorización, no solo en la conveniencia del frontend.
  • Incluir campos como ownerId, institutionId o tenantId cuando faciliten reglas seguras y queries coherentes.[cite:3]
  • Usar funciones para evitar duplicación y mejorar mantenibilidad.[cite:2]
  • Evitar reglas globales demasiado amplias como allow read, write: if request.auth != null; para producción.[cite:1]
  • Usar get() y exists() con criterio, recordando límites de acceso y costo por lectura.[cite:2]
  • Recordar que las reglas no son filtros; la query debe respetar la regla.[cite:2][cite:3]
  • Validar cambios sensibles comparando request.resource.data contra resource.data.[cite:2]
  • Desplegar reglas con CLI y mantenerlas bajo control de versiones junto al proyecto.[cite:1]
  • Probar reglas antes de publicar usando simulador y pruebas reales de flujo.[cite:1]

Errores comunes

  • Pensar que autenticación equivale a autorización.
  • Publicar con allow read, write: if true;, algo que Firebase advierte que nunca debe usarse en producción.[cite:1]
  • Confiar en validaciones del cliente como si fueran seguridad real.
  • Intentar usar reglas como filtros de resultados.[cite:2][cite:3]
  • No incluir en los documentos los campos necesarios para autorizar correctamente.
  • Construir queries que no respetan las restricciones impuestas por las reglas.[cite:3]
  • Abusar de get() y exists() hasta golpear límites o aumentar costos.[cite:2]
  • No separar operaciones create, update y delete cuando requieren políticas distintas.
  • Permitir que el cliente cambie rol, institución o propietario de un documento mediante updates.
  • No considerar el tiempo de propagación de cambios de reglas; Firebase indica que las actualizaciones pueden tardar hasta un minuto en afectar nuevas queries y listeners, y hasta 10 minutos en propagarse completamente a listeners activos.[cite:1]

Checklist para producción

Antes de publicar Firestore en producción, revisar:

  • ¿El archivo comienza con rules_version = '2';?[cite:1]
  • ¿Existe alguna regla global excesivamente permisiva?
  • ¿Toda colección sensible exige autenticación?
  • ¿La autorización distingue roles, propietarios e institución cuando corresponde?
  • ¿Se separaron get y list en colecciones donde las consultas necesitan límites específicos?[cite:3][cite:4]
  • ¿Las queries del frontend cumplen exactamente las restricciones de las reglas?[cite:2][cite:3]
  • ¿Los campos sensibles no pueden ser modificados por el cliente?
  • ¿Los roles o claims están bien modelados?
  • ¿El uso de get() y exists() es realmente necesario y está bajo control?[cite:2]
  • ¿Se contemplaron límites de access calls por evaluación?[cite:2]
  • ¿Las reglas fueron probadas con usuarios autenticados, no autenticados y roles diferentes?[cite:1]
  • ¿El despliegue de reglas forma parte del flujo profesional del proyecto mediante CLI?[cite:1]

Resumen

Las Firestore Security Rules son el corazón de la autorización en aplicaciones Firebase que acceden a la base de datos desde clientes. Cada solicitud web o móvil se evalúa contra ellas antes de leer o escribir datos, y si una ruta solicitada no cumple la condición correspondiente, la operación completa falla.[cite:1]

Su diseño exige entender varias piezas internas: request.auth aporta identidad y claims, resource.data representa el documento actual, request.resource.data representa el estado futuro de una escritura y request.query permite imponer restricciones sobre consultas como limit y orderBy.[cite:2][cite:3][cite:4] Con esas herramientas, las reglas pueden proteger propietarios, roles, instituciones, campos sensibles y ventanas temporales.

Sin embargo, las reglas no son filtros ni sustituyen un diseño correcto de datos. Firebase advierte que las consultas deben ajustarse a las restricciones del ruleset y que funciones como get() y exists() consumen lecturas facturables además de estar sujetas a límites por evaluación.[cite:2][cite:3] Por eso la seguridad, el rendimiento y el costo están profundamente conectados.

En el proyecto oficial del libro, un modelo robusto basado en autenticación, roles, claims e institución permite proteger usuarios, docentes, alumnos, grupos, tareas, evidencias, credenciales y configuraciones con un criterio profesional. Ese es el objetivo real del capítulo: que el lector deje de ver las reglas como una lista de permisos y empiece a tratarlas como una capa arquitectónica esencial para publicar Firestore con seguridad en producción.

Conceptos clave

  • Firestore Security Rules.[cite:1]
  • Autenticación vs autorización.[cite:1][cite:2]
  • request.auth.[cite:2][cite:4]
  • resource.data.[cite:2]
  • request.resource.data.[cite:2][cite:4]
  • request.time.[cite:4]
  • request.path.[cite:4]
  • request.query.[cite:3][cite:4]
  • match.[cite:1]
  • allow.[cite:1]
  • function.[cite:2]
  • get().[cite:2]
  • exists().[cite:2]
  • getAfter().[cite:2]
  • get, list, create, update, delete.[cite:4]
  • Rules Version 2.[cite:1]
  • Custom Claims.[cite:4]
  • Principio de mínimo privilegio.
  • Reglas no son filtros.[cite:2][cite:3]

Preguntas de repaso

  1. ¿Qué son las Firestore Security Rules y en qué momento se evalúan?[cite:1]
  2. ¿Cuál es la diferencia entre autenticación y autorización en Firebase?[cite:1][cite:2]
  3. ¿Qué diferencia existe entre resource.data y request.resource.data?[cite:2][cite:4]
  4. ¿Por qué conviene separar get y list en reglas avanzadas?[cite:3][cite:4]
  5. ¿Qué significa que las reglas no son filtros?[cite:2][cite:3]
  6. ¿Qué límites existen para llamadas de acceso a documentos dentro de una evaluación de reglas?[cite:2]
  7. ¿Qué costo oculto puede tener el uso de get() y exists()?[cite:2]
  8. ¿Qué ventajas ofrecen los Custom Claims frente a leer siempre documentos auxiliares?[cite:4]
  9. ¿Por qué Rules Version 2 es obligatoria para collection group queries?[cite:1][cite:3]
  10. ¿Qué reglas del proyecto oficial deberían delegar escrituras a Cloud Functions en vez de permitir modificación directa desde cliente?

Ejercicios prácticos

  1. Escribe una regla para que cada usuario solo pueda leer y actualizar su propio documento de perfil.[cite:2]
  2. Crea una regla que impida cambiar el campo role una vez creado un documento.
  3. Diseña un modelo de claims para superadmin, institutionAdmin, teacher y student.
  4. Implementa reglas para que un alumno solo vea sus evidencias y un docente solo las de sus grupos.
  5. Diseña una regla list que limite consultas a 20 documentos usando request.query.limit.[cite:3][cite:4]
  6. Reescribe un ruleset repetitivo usando funciones reutilizables.[cite:2]
  7. Diseña el ruleset de credentials para que solo Cloud Functions pueda crear o modificar credenciales y el usuario solo pueda leer la suya.
  8. Modela una colección multiempresa con tenantId o institutionId y define reglas acordes.
  9. Prueba tus reglas en el simulador de Firebase Console con un usuario autenticado, uno no autenticado y un administrador.[cite:1]
  10. Elabora una auditoría de riesgos para un proyecto que hoy usa allow read, write: if request.auth != null; en toda la base de datos.

Bibliografía y referencias oficiales