20

Reglas de Seguridad Avanzadas con Firebase y Firestore

212512Puntos

hace 5 años

Firestore es una base de datos no relacional muy flexible de la familia de Firebase y gracias las reglas de seguridad podemos administrar el acceso y las capacidades de nuestros usuarios para que puedan o no realizar ciertas acciones en nuestra base de datos.

Estas reglas son muy sencillas de configurar pero también son muy poderosas. Firebase se encarga del trabajo pesado. Solo debemos conocer muy bien nuestra aplicación y comprobar que las reglas realmente pueden proteger la integridad de nuestras aplicaciones.

Modelo de Datos en Firestore: Documentos y Colecciones

Tenemos diferentes tipos de objetos para construir nuestras aplicaciones con Firestore: colecciones y documentos sencillos o complejos.

Documentos: Los documentos (sencillos) son diccionarios de datos que podemos guardar en formato de llave => valor. Además, podemos crear documentos anidados, algo así como subdocumentos. Si estás acostumbrado al mundo de JavaScript puedes entender estos documentos complejos como objetos; cada objeto puede tener más objetos y así sucesivamente.

Sin embargo, los documentos complejos también pueden volver nuestras aplicaciones --aún-- más complejas. Firebase nos permite hacer consultas por documentos, esto significa que si tenemos una cascada de subdocumentos nuestras consultas van a tomar cada vez más tiempo y van a ser más difíciles de mantener.

Colecciones: Las colecciones son conjuntos de documentos. Podemos tener una colección de usuarios, una colección para las pizzas, otra para tipos de animales, etc. Más que todo sirven para organizar nuestra información. La recomendación es no tener ni más ni menos colecciones de las que realmente necesitamos.

Al tener muchas colecciones, por no guardar todo en un mismo sitio, vamos a necesitar referencias que nos indiquen dónde conseguir los datos que hacen falta. Esto significa que debemos hacer demasiadas consultas para conseguir la información suficiente. Vamos a gastar mucho tiempo entre consulta y consulta.

De la misma forma, si usamos muy pocas colecciones (abusando de los documentos anidados), cada consulta va a tomar más tiempo de lo que podemos esperar y vamos a recibir mucha información que realmente no vamos a utilizar.

En resumen: Usa Firestore con responsabilidad.

Reglas de seguridad

Las reglas de seguridad nos ayudan a determinar quién puede o tiene permisos para hacer algo: leer, escribir, crear, actualizar, borrar, entre otras. Podemos restringir el acceso a toda una colección o a documentos específicos dependiendo de nuestra lógica de negocios.

Debemos entender muy bien cómo funcionan nuestras aplicaciones y personalizar estas reglas con diferentes combinaciones hasta “asegurarnos” que realmente funcionan como debe ser.

Cómo crear una regla de seguridad:

  • Service: Todas las reglas comienzan especificando el servicio sobre el cual deben actuar. En este caso, debemos indicarle a Firebase que queremos usar Firestore con la siguiente expresión:
servicecloud.firestore {
  ...
}
  • Match: Indicamos las colecciones y documentos sobre los cuales deben a aplicar nuestras reglas de seguridad. Podemos tener múltiples expresiones match. Incluso, unas dentro de otras:
service cloud.firestore {
  match /databases/{database}/documents {
      match /<some_path>/ {
        ...
      }
      match /<some_other_path>/ {
        ...
      }
  }
}
  • Allow: Definimos los permisos para la “ruta” de colecciones y documentos que definimos anteriormente. Podemos usar las expresiones read, write, create, update o delete seguido de dos puntos (:), la expresión if y la condición que debemos cumplir para otorgar o denegar el permiso:
service cloud.firestore {
  match /databases/{database}/documents {
      match /test/ {
        allow read, write: iftrue;
      }
      match /<some_other_path>/ {
        allow create, delete: if false;
      }
  }
}

Siempre debemos escribir un punto y coma ; al final.

Afortunadamente, no solo podemos usar las expresiones true y false para limitar el acceso a ciertas partes de nuestra base de datos; también podemos acceder a la información de los usuarios que realizan las peticiones, los datos guardados en los documentos que queremos actualizar y los nuevos campos que los usuarios envían para editar los documentos.

Ejemplos de Reglas de Seguridad

A continuación, vamos a ver algunos ejemplos de reglas de seguridad para casos muy específicos. Pueden ser de utilidad para ti, tu equipo o tu aplicación.

  • Denegar el acceso de lectura y escritura a todos los usuarios en cualquier condición:
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: iffalse;
    }
  }
}
  • Permitir el acceso de lectura y escritura a todos los usuarios en cualquier condición (!NUNCA USES ESTA REGLA EN PRODUCCIÓN!):
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: iftrue;
    }
  }
}
  • Usuarios: Permitir acceso de lectura y escritura en todos los documentos a cualquier usuario autenticado:
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.auth.id != null;
    }
  }
}
  • Usuarios: Permite la escritura en el documento de usuarios solo si el usuarios que queremos modificar es el mismo usuario que intenta modificar su propia información:
service cloud.firestore {
  match /databases/{database}/documents {
    match /users {
      allow read, write: if request.resource.data.username == resource.data.username;
    }
  }
}
  • Caso real de usuarios:
    1️⃣ Permite la creación de usuarios si estamos autenticados (porque si el usuario no existe, no podemos verificar los IDs).
    2️⃣ Permite la escritura en el documento de usuarios solo si el usuarios que queremos modificar es el mismo usuario que intenta modificar su propia información:
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      // Podemos modificar la data del usuario si el userId del modificador// es el mismo del usuario a modificar...
      allow read, update, delete: if request.auth.uid == userID;

      // Permite crear usuarios si estamos autenticados porque, si el usuario// no existe, no podemos verificar su ID...
      allow create: if request.auth.uid != null;
    }
  }
}
  • Caso real con ciudades:
    1️⃣ No permite la actualización si cambia el nombre de la ciudad.
    2️⃣ Permite modificar la información de las ciudades siempre y cuando siga teniendo poblacion.
service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      allow update: if request.resource.data.population > 0
        && request.resource.data.name == resource.data.name;
    }
  }
}
  • Blog: Publicaciones y Borradores: Permitir que los campos is_public y can_edit de cada documento determinen si la información puede ser leída o actualizada y por quién:
service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      allow read: resource.data.is_public == true;
      allow read: resource.data.can_edit == "any";
    }
  }
}

Bonus: El Simulador

Firebase nos ofrece un simulador: una herramienta para comprobar que nuestras reglas de seguridad permiten o restringen el acceso a nuestros datos de manera correcta. No dudes en probar estas y todas las combinaciones de reglas de seguridad que se te ocurran.

Firebase: Simulador de Reglas de Seguridad

Sin embargo, recuerda que no podemos confiar al 100% en este simulador. La mejor forma de comprobar que nuestras reglas realmente funcionan como esperamos es programando el código de nuestras aplicaciones y probar sin miedo 🔥.

Conclusiones

Las reglas de seguridad en Firestore son una de mis características favoritas entre todos los servicios de Firebase. Vale totalmente la pena invertir un buen tiempo en crearlas y organizarlas correctamente.

Puedes aprender mucho más sobre los servicios de Firestore y Firebase Authentication con el Curso de Firestore para Android. Vamos a construir una aplicación móvil para consumir cripto-monedas en tiempo real. Nuestro profesor será Santiago Carrillo, @sancarbar, Google Developer Expert en Android con más de 7 años de experiencia creando aplicaciones.

Te invito a estudiar las mejores prácticas de todos estos servicios. Ya sabes, un gran poder conlleva una gran responsabilidad.

#NuncaParesDeAprender 🤓💚

Juan
Juan
juandc

212512Puntos

hace 5 años

Todas sus entradas
Escribe tu comentario
+ 2
Ordenar por:
3
4818Puntos

alguna regla para no permitir datos duplicados?

1
19465Puntos
4 años

Me interesa, descubriste alguna?

2
818Puntos
3 años

Dentro de las reglas de seguridad puedes crear funciones y metodos get, sin embargo creo que hay un máximo de llamadas

2
7625Puntos

Excelente articulo, muy util.

1
2215Puntos

Buen dia, estoy con reglas de firestore, para la creación me funciona pero para el borrado no, aunque según yo debería ser lo mismo, lo que intento hacer es que solo permita crear y borrar documentos si el documento contiene en un campo el ID del usuario autenticado o bien si es un usuario que sea Admin, no entiendo por que para crear si me funciona pero para borrar me marca error 😦 ¿que cambia para el borrado?

Aquí el ejemplo, repito el write funciona, el delete no 😦

match /pagos/{year}/pagos/{id} {
allow write: if request.auth.uid == request.resource.data.alumnoId ||
get(/databases/$(database)/documents/users/$(request.auth.uid)).data.isAdmin;

allow delete: if request.auth.uid == request.resource.data.alumnoId ||
get(/databases/$(database)/documents/users/$(request.auth.uid)).data.isAdmin;

}

2
17687Puntos
4 años

Puedes utilizar resource.data.alumnoId en vez del
del request.resource.data.alumnoId para hacer el delete.