← Guías por stack

Firebase

Seguridad para tu app con Firebase

La seguridad de Firebase depende casi por completo de las reglas que escribas. Con las reglas en modo test o demasiado abiertas, tu base de datos queda al alcance de cualquiera. Revísalo antes de abrir tu app.

Riesgos típicos en Firebase

Reglas en modo test

El modo de prueba permite lectura y escritura a todo el mundo. Mucha gente lanza sin cambiarlo, dejando la base de datos completamente abierta.

Confundir la clave pública con seguridad

La config de Firebase es pública por diseño; lo que protege tus datos son las reglas, no ocultar esa clave. Asumir lo contrario lleva a datos expuestos.

Otras claves en el frontend

Junto a la config de Firebase suelen colarse claves que sí son secretas (pagos, correo, IA). Esas no deben estar nunca en el JavaScript del navegador.

"Seguridad en Firebase" son dos cosas que conviene separar. La primera es el modelo de Firebase: el navegador habla directamente con la base de datos y el almacenamiento, así que no hay un servidor que filtre, y la protección vive en las reglas, en App Check y en la configuración del proyecto. La segunda es lo que Lovable, Bolt o Cursor hacen con Firebase al generar código, que es donde aparecen casi todos los agujeros. Esta guía explica el modelo, repasa lo que la IA hace mal, te deja un firebase.json y unas reglas base para pegar, un prompt de revisión y la comprobación desde fuera. Los diez errores de reglas más comunes, con la regla mala y la buena, están en los errores de seguridad más comunes en Firebase.

Parte 1: cómo funciona la seguridad en Firebase

El cliente habla con los servicios, no con tu servidor

En una app Firebase típica no hay backend propio: el SDK del navegador lee y escribe en Firestore, Realtime Database y Storage directamente. Eso significa que el objeto de configuración con apiKey y projectId es público por diseño (identifica el proyecto, no autoriza nada) y que la única barrera entre un visitante y tus datos son las reglas de seguridad. Cada producto tiene las suyas: Firestore, Realtime Database y Storage se configuran por separado, y una app suele tener abierto el que nadie miró.

Las reglas responden a "quién puede qué"

Una regla recibe la petición con el usuario autenticado (request.auth), el documento que se quiere leer o escribir y los datos nuevos (request.resource.data). Con eso decide. La distinción clave es entre autenticado (hay sesión) y autorizado (esa sesión puede ver ese documento): request.auth != null solo comprueba lo primero, y registrarse es gratis. Las reglas también validan lo que se escribe, para que un usuario no se ponga isAdmin: true en su propio perfil.

Lo que protege además de las reglas

  • App Check. Exige que las peticiones vengan de tu app real (con reCAPTCHA o Play Integrity) y no de un script con tu projectId. Frena el abuso y las facturas inesperadas por lecturas masivas.
  • Restricción de la clave de API. En la consola de Google Cloud, limita la clave a los dominios de tu web y a las APIs que usas. No sustituye a las reglas, pero cierra una puerta.
  • Auth. Dominios autorizados para el inicio de sesión, protección contra enumeración de emails, verificación de correo, métodos de acceso que realmente usas y nada más, autenticación multifactor si manejas datos sensibles.
  • Cloud Functions como servidor. Todo lo que necesite una clave secreta (pagos, correo, IA) va en una función, con el secreto en Secret Manager, y el cliente llama a la función. Ojo: una función HTTP es pública para todo internet salvo que compruebes el token del usuario o uses una función callable con App Check.
  • Presupuesto. Firebase cobra por lectura y por invocación. Alertas de presupuesto en Google Cloud y límites en las reglas (request.query.limit) evitan que un bucle o un atacante te dejen una factura.

Hosting: lo que Firebase pone y lo que no

Firebase Hosting da TLS automático, CDN y redirección a HTTPS. No pone cabeceras de seguridad: se añaden en firebase.json. Y sirve todo lo que haya en la carpeta pública, incluido un .env copiado por error.

Parte 2: lo que la IA hace mal con Firebase

  • Reglas en modo de prueba, y luego if true. La IA deja las reglas que abren todo durante treinta días; al caducar, la app falla y la solución rápida es abrirlas para siempre.
  • request.auth != null como única condición. Cualquier usuario registrado lee los datos de todos los demás.
  • Escrituras sin validar. El perfil tiene role o credits y la regla de update no excluye esos campos.
  • Otras claves en el bundle. Junto a la configuración de Firebase aparecen la clave secreta de Stripe, la de OpenAI o la de un servicio de correo. Detalle en secretos que tu app filtra en el navegador.
  • Funciones HTTP sin comprobar al usuario. La función que cobra, envía correos o llama a un modelo responde a cualquiera que conozca la URL.
  • Storage con reglas copiadas de Firestore. allow read, write: if request.auth != null en storage.rules: cualquier usuario lee los archivos de los demás y sube lo que quiera.
  • Sin App Check, sin cabeceras, sin alertas. Tres casillas que nadie marca porque la app "ya funciona".

Un bloque para pegar en tu proyecto

Cabeceras de seguridad en firebase.json (la Content-Security-Policy va aparte porque depende de tus scripts):

{
  "hosting": {
    "public": "dist",
    "headers": [
      {
        "source": "**",
        "headers": [
          { "key": "Strict-Transport-Security", "value": "max-age=63072000; includeSubDomains; preload" },
          { "key": "X-Content-Type-Options", "value": "nosniff" },
          { "key": "X-Frame-Options", "value": "DENY" },
          { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
          { "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" }
        ]
      }
    ]
  }
}

Y un punto de partida para firestore.rules: todo cerrado por defecto, cada usuario su documento y campos de privilegio protegidos.

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow read: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null && request.auth.uid == userId
        && !request.resource.data.keys().hasAny(['role', 'isAdmin', 'plan', 'credits']);
      allow update: if request.auth != null && request.auth.uid == userId
        && !request.resource.data.diff(resource.data).affectedKeys()
           .hasAny(['role', 'isAdmin', 'plan', 'credits']);
    }
    match /{document=**} {
      allow read, write: if false;
    }
  }
}

Añade después una regla explícita por colección; la última, con if false, garantiza que cualquier colección nueva nace cerrada.

Un prompt de revisión antes de publicar

Revisa la seguridad de esta app con Firebase y no cambies nada todavía. Responde con una lista:
1. Reglas de Firestore, Realtime Database y Storage que permitan acceso a cualquiera, con fecha de modo de prueba o con request.auth != null como única condición.
2. Reglas de escritura que no validen los campos, en especial role, isAdmin, plan o credits.
3. Claves que no sean la configuración de Firebase presentes en el código del navegador, con archivo y línea.
4. Cloud Functions HTTP que no verifiquen el token del usuario, y funciones que lean secretos del código en lugar de Secret Manager.
5. Si App Check está activado y si la clave de API está restringida por dominio.
6. Cabeceras de seguridad en firebase.json y archivos en la carpeta pública que no deban estar ahí.
Para cada punto, propón la regla o el cambio exacto y espera mi confirmación.

Comprueba el resultado desde fuera

Tu projectId está en el bundle, así que cualquiera puede hacer esto. La API REST de Firestore sin token:

curl -s "https://firestore.googleapis.com/v1/projects/TU_PROYECTO/databases/(default)/documents/users" | head -c 400
curl -sI https://tudominio.com | grep -iE "strict-transport|x-frame|x-content-type"

Lo correcto es un error de permisos en la primera (si devuelve documentos, la colección está abierta) y las cabeceras en la segunda. Repite la primera con cada colección y con Storage. Scanaris hace estas comprobaciones y unas cuarenta más en 30 segundos: detecta la configuración de Firebase en tu bundle, prueba las colecciones habituales sin autenticación, busca otras claves en el JavaScript, revisa cabeceras, CORS, cookies y archivos expuestos, y te da un fix prompt por cada hallazgo para pegar en Lovable, Bolt o Cursor. Si tu app usa Supabase en lugar de Firebase, la guía de Supabase es el equivalente.

Preguntas frecuentes

¿Tengo que esconder la apiKey de Firebase?

No. Es pública por diseño e identifica el proyecto. Lo que protege los datos son las reglas y App Check. Lo que sí conviene es restringirla a tus dominios en Google Cloud.

¿Las Cloud Functions son privadas?

Una función HTTP es una URL pública. Comprueba el token del usuario dentro de la función o usa funciones callable con App Check. Las funciones que reaccionan a eventos (un documento creado, un archivo subido) no tienen URL y sí son internas.

¿Cómo pruebo las reglas sin romper la app?

Con el simulador de la consola (pestaña Reglas) para probar lecturas concretas, y con el emulador local para tests automáticos. Cambia las reglas con la app abierta en otra pestaña y comprueba que lo que debe funcionar sigue funcionando.

Checks relacionados

¿Tu app Firebase pasa estos checks?

Escanéala gratis y recibe un prompt de arreglo por cada fallo que encontremos.

Escanear mi web gratis