TechLead
Lección 8 de 8
6 min de lectura
Firebase

Reglas de seguridad de Firebase

Protege tu base de datos y almacenamiento con reglas de seguridad potentes y flexibles

¿Qué son las reglas de seguridad de Firebase?

Firebase Security Rules ofrecen un lenguaje flexible basado en expresiones para definir quién tiene acceso de lectura y escritura a datos en Firestore, Realtime Database y Cloud Storage. Las reglas se evalúan en los servidores de Firebase, por lo que no pueden ser omitidas por clientes maliciosos.

Las reglas de seguridad están entre tus datos y usuarios maliciosos. Determinan qué datos se pueden leer y escribir, validan la estructura de datos y aplican lógica de negocio—sin requerir un servidor backend.

🔒 Por qué importan las reglas

  • 🛡️ Protección de datos: Evita accesos no autorizados a datos sensibles.
  • ✅ Validación de datos: Asegura que los datos cumplan tu esquema.
  • 👤 Aislamiento de usuarios: Mantén privados los datos de cada usuario.
  • 🚫 Limitación de abusos: Previene abuso con límites de tamaño y frecuencia.

Reglas de seguridad de Firestore

Estructura básica de las reglas de Firestore:

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

    // Match a specific collection
    match /users/{userId} {
      // Allow read if user is authenticated
      allow read: if request.auth != null;

      // Allow write only to own document
      allow write: if request.auth != null && request.auth.uid == userId;
    }
  }
}

Patrones comunes de reglas en Firestore

Ejemplos prácticos para escenarios comunes:

Datos propios del usuario

match /users/{userId} {
  // Users can only read/write their own data
  allow read, write: if request.auth != null && request.auth.uid == userId;
}

match /posts/{postId} {
  // Anyone can read posts
  allow read: if true;

  // Only the author can create/update/delete
  allow create: if request.auth != null
    && request.resource.data.authorId == request.auth.uid;

  allow update, delete: if request.auth != null
    && resource.data.authorId == request.auth.uid;
}

Acceso basado en roles

match /admin/{document=**} {
  // Only allow access if user has admin custom claim
  allow read, write: if request.auth != null
    && request.auth.token.role == 'admin';
}

match /moderator-content/{docId} {
  // Allow access to admins and moderators
  allow read, write: if request.auth != null
    && request.auth.token.role in ['admin', 'moderator'];
}

Validación de datos

match /posts/{postId} {
  allow create: if request.auth != null
    // Required fields exist
    && request.resource.data.keys().hasAll(['title', 'content', 'authorId'])
    // Title length validation
    && request.resource.data.title is string
    && request.resource.data.title.size() >= 1
    && request.resource.data.title.size() <= 100
    // Content validation
    && request.resource.data.content is string
    && request.resource.data.content.size() <= 10000
    // Author must be the current user
    && request.resource.data.authorId == request.auth.uid
    // Timestamp must be server time
    && request.resource.data.createdAt == request.time;

  allow update: if request.auth != null
    && resource.data.authorId == request.auth.uid
    // Prevent changing certain fields
    && request.resource.data.authorId == resource.data.authorId
    && request.resource.data.createdAt == resource.data.createdAt;
}

Funciones en reglas de Firestore

Crea funciones reutilizables para reglas más limpias:

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

    // Helper function: Check if user is authenticated
    function isAuthenticated() {
      return request.auth != null;
    }

    // Helper function: Check if user owns the resource
    function isOwner(userId) {
      return isAuthenticated() && request.auth.uid == userId;
    }

    // Helper function: Check if user has a specific role
    function hasRole(role) {
      return isAuthenticated() && request.auth.token.role == role;
    }

    // Helper function: Check if user is admin
    function isAdmin() {
      return hasRole('admin');
    }

    // Helper function: Validate string length
    function isValidString(field, minLen, maxLen) {
      return field is string
        && field.size() >= minLen
        && field.size() <= maxLen;
    }

    // Helper function: Get user document
    function getUserData() {
      return get(/databases/$(database)/documents/users/$(request.auth.uid)).data;
    }

    // Use the functions
    match /users/{userId} {
      allow read: if isAuthenticated();
      allow write: if isOwner(userId);
    }

    match /admin/{document=**} {
      allow read, write: if isAdmin();
    }

    match /posts/{postId} {
      allow read: if true;
      allow create: if isAuthenticated()
        && isValidString(request.resource.data.title, 1, 100);
    }
  }
}

Reglas para subcolecciones

Asegura colecciones anidadas:

match /users/{userId} {
  allow read, write: if request.auth.uid == userId;

  // Subcollection: user's private notes
  match /notes/{noteId} {
    // Inherit parent's rules - only owner can access
    allow read, write: if request.auth.uid == userId;
  }

  // Subcollection: user's public posts
  match /publicPosts/{postId} {
    // Anyone can read, only owner can write
    allow read: if true;
    allow write: if request.auth.uid == userId;
  }
}

// Wildcard to match any subcollection depth
match /users/{userId}/{document=**} {
  allow read, write: if request.auth.uid == userId;
}

Reglas de seguridad de Cloud Storage

Asegura subidas y descargas de archivos:

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {

    // User profile images
    match /users/{userId}/avatar.{ext} {
      // Anyone can read profile images
      allow read: if true;

      // Only owner can upload, with restrictions
      allow write: if request.auth != null
        && request.auth.uid == userId
        // Only allow image files
        && request.resource.contentType.matches('image/.*')
        // Max 5MB file size
        && request.resource.size < 5 * 1024 * 1024;
    }

    // User's private files
    match /users/{userId}/private/{allPaths=**} {
      allow read, write: if request.auth != null
        && request.auth.uid == userId;
    }

    // Public uploads (e.g., blog images)
    match /public/{allPaths=**} {
      // Anyone can read
      allow read: if true;

      // Only authenticated users can upload
      allow write: if request.auth != null
        && request.resource.contentType.matches('image/.*')
        && request.resource.size < 10 * 1024 * 1024;
    }

    // Restrict file types
    match /documents/{userId}/{fileName} {
      allow read: if request.auth.uid == userId;
      allow write: if request.auth.uid == userId
        // Allow only PDF and common document formats
        && request.resource.contentType in [
          'application/pdf',
          'application/msword',
          'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
        ];
    }
  }
}

Reglas de seguridad de Realtime Database

Reglas basadas en JSON para Realtime Database:

{
  "rules": {
    // Default: deny all access
    ".read": false,
    ".write": false,

    "users": {
      "$userId": {
        // Users can read their own data
        ".read": "$userId === auth.uid",

        // Users can write their own data with validation
        ".write": "$userId === auth.uid",

        // Validate user data structure
        ".validate": "newData.hasChildren(['name', 'email'])",

        "name": {
          ".validate": "newData.isString() && newData.val().length <= 50"
        },

        "email": {
          ".validate": "newData.isString() && newData.val().matches(/^[^@]+@[^@]+$/)"
        }
      }
    },

    "posts": {
      // Anyone can read posts
      ".read": true,

      "$postId": {
        // Only authenticated users can create posts
        ".write": "auth != null && (!data.exists() || data.child('authorId').val() === auth.uid)",

        ".validate": "newData.hasChildren(['title', 'content', 'authorId'])",

        "authorId": {
          ".validate": "newData.val() === auth.uid"
        }
      }
    },

    "presence": {
      "$userId": {
        // Users can only update their own presence
        ".write": "$userId === auth.uid"
      }
    }
  }
}

Probar reglas de seguridad

Prueba tus reglas antes de desplegar:

# Use the Firebase Emulator
firebase emulators:start

# Run rules tests
npm test

# Test file: tests/firestore.rules.test.js
const { initializeTestEnvironment, assertFails, assertSucceeds } = require('@firebase/rules-unit-testing');

let testEnv;

beforeAll(async () => {
  testEnv = await initializeTestEnvironment({
    projectId: 'demo-project',
    firestore: {
      rules: fs.readFileSync('firestore.rules', 'utf8')
    }
  });
});

afterAll(async () => {
  await testEnv.cleanup();
});

test('users can read their own data', async () => {
  const userId = 'user123';
  const context = testEnv.authenticatedContext(userId);
  const db = context.firestore();

  await assertSucceeds(db.collection('users').doc(userId).get());
});

test('users cannot read other users data', async () => {
  const context = testEnv.authenticatedContext('user123');
  const db = context.firestore();

  await assertFails(db.collection('users').doc('other-user').get());
});

test('unauthenticated users cannot write', async () => {
  const context = testEnv.unauthenticatedContext();
  const db = context.firestore();

  await assertFails(db.collection('users').doc('test').set({ name: 'Test' }));
});

Desplegar reglas

Despliega tus reglas de seguridad:

# Deploy Firestore rules
firebase deploy --only firestore:rules

# Deploy Storage rules
firebase deploy --only storage

# Deploy Realtime Database rules
firebase deploy --only database

# Deploy all rules
firebase deploy --only firestore:rules,storage,database

# View deployed rules in Firebase Console
# Firestore: Firebase Console > Firestore > Rules
# Storage: Firebase Console > Storage > Rules
# Realtime DB: Firebase Console > Realtime Database > Rules

Errores de seguridad comunes

Evita estos errores frecuentes:

Nunca hagas esto:

// DANGEROUS: Allows anyone to read/write everything
match /{document=**} {
  allow read, write: if true;
}

// DANGEROUS: Only checks authentication, not authorization
match /users/{userId} {
  allow read, write: if request.auth != null;
}

Haz esto en su lugar:

// SECURE: Check both authentication AND authorization
match /users/{userId} {
  allow read, write: if request.auth != null
    && request.auth.uid == userId;
}

// SECURE: Validate all incoming data
match /posts/{postId} {
  allow create: if request.auth != null
    && request.resource.data.authorId == request.auth.uid
    && request.resource.data.title is string
    && request.resource.data.title.size() > 0;
}

💡 Puntos clave

  • • Las reglas de seguridad son la primera línea de defensa contra accesos no autorizados
  • • Valida siempre autenticación y autorización
  • • Usa funciones para mantener reglas claras y reutilizables
  • • Valida estructura y contenido de datos en reglas de escritura
  • • Prueba las reglas antes de desplegar en producción
  • • Nunca uses allow read, write: if true en producción

📚 Más recursos

Continuar Aprendiendo