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¶
- 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]
- 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.
- 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]
- 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. - Casos de uso. Deben presentarse como imágenes didácticas para resumir plataformas educativas, paneles administrativos y escenarios multiempresa.
Diagramas SVG¶
- 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]
- Arquitectura completa de autorización. Requiere SVG porque integra clientes, Authentication, reglas, documentos de soporte, claims y base de datos.
- 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] - 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.authy 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()ygetAfter().[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:
- El cliente envía una lectura o escritura.
- Firestore identifica la ruta y el tipo de operación.
- Busca los bloques
matchaplicables. - Evalúa los
allowque correspondan a esa operación. - Resuelve funciones y expresiones usadas en la condición.
- 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.
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:
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¶
- El cliente intenta acceder a
/users/abc123. - Firestore evalúa el bloque
matchcorrespondiente. - Comprueba si hay autenticación (
request.auth != null).[cite:2] - Compara
request.auth.uidcon el wildcard{userId}.[cite:2] - 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:
Consulta inválida:
Consulta válida:
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¶
- Toda operación exige autenticación salvo recursos explícitamente públicos.
- El acceso se restringe por institución.
- Los claims sirven para autorización rápida global.
- Los documentos contienen campos mínimos que facilitan autorización segura:
institutionId,ownerId,teacherId,studentId,visibility,role. - 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
getylistcuando 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,institutionIdotenantIdcuando 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()yexists()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.datacontraresource.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()yexists()hasta golpear límites o aumentar costos.[cite:2] - No separar operaciones
create,updateydeletecuando 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
getylisten 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()yexists()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¶
- ¿Qué son las Firestore Security Rules y en qué momento se evalúan?[cite:1]
- ¿Cuál es la diferencia entre autenticación y autorización en Firebase?[cite:1][cite:2]
- ¿Qué diferencia existe entre
resource.datayrequest.resource.data?[cite:2][cite:4] - ¿Por qué conviene separar
getylisten reglas avanzadas?[cite:3][cite:4] - ¿Qué significa que las reglas no son filtros?[cite:2][cite:3]
- ¿Qué límites existen para llamadas de acceso a documentos dentro de una evaluación de reglas?[cite:2]
- ¿Qué costo oculto puede tener el uso de
get()yexists()?[cite:2] - ¿Qué ventajas ofrecen los Custom Claims frente a leer siempre documentos auxiliares?[cite:4]
- ¿Por qué Rules Version 2 es obligatoria para collection group queries?[cite:1][cite:3]
- ¿Qué reglas del proyecto oficial deberían delegar escrituras a Cloud Functions en vez de permitir modificación directa desde cliente?
Ejercicios prácticos¶
- Escribe una regla para que cada usuario solo pueda leer y actualizar su propio documento de perfil.[cite:2]
- Crea una regla que impida cambiar el campo
roleuna vez creado un documento. - Diseña un modelo de claims para
superadmin,institutionAdmin,teacherystudent. - Implementa reglas para que un alumno solo vea sus evidencias y un docente solo las de sus grupos.
- Diseña una regla
listque limite consultas a 20 documentos usandorequest.query.limit.[cite:3][cite:4] - Reescribe un ruleset repetitivo usando funciones reutilizables.[cite:2]
- Diseña el ruleset de
credentialspara que solo Cloud Functions pueda crear o modificar credenciales y el usuario solo pueda leer la suya. - Modela una colección multiempresa con
tenantIdoinstitutionIdy define reglas acordes. - Prueba tus reglas en el simulador de Firebase Console con un usuario autenticado, uno no autenticado y un administrador.[cite:1]
- 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¶
- Get started with Cloud Firestore Security Rules: https://firebase.google.com/docs/firestore/security/get-started [cite:1]
- Writing conditions for Cloud Firestore Security Rules: https://firebase.google.com/docs/firestore/security/rules-conditions [cite:2]
- Securely query data: https://firebase.google.com/docs/firestore/security/rules-query [cite:3]
- Rules reference — Request interface: https://firebase.google.com/docs/reference/rules/rules.firestore.Request [cite:4]