Skip to content

Firebase Security Rules Best Practices 2026

Firebase security best practices evolve as new attack vectors emerge and the platform matures. This guide covers current best practices for securing Firebase applications in 2026.

Core Principles

1. Default Deny, Explicit Allow

Always start with deny-all rules:

javascript
// Firestore
service cloud.firestore {
  match /databases/{database}/documents {
    // Deny everything by default
    match /{document=**} {
      allow read, write: if false;
    }

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

2. Principle of Least Privilege

Grant minimum necessary permissions:

javascript
// Bad: Too broad
allow read, write: if request.auth != null;

// Good: Specific permissions
allow get: if request.auth.uid == userId;
allow list: if false;  // No listing
allow create: if request.auth.uid == userId;
allow update: if request.auth.uid == userId
  && !request.resource.data.diff(resource.data).affectedKeys().hasAny(['role']);
allow delete: if false;  // No deletion

3. Defense in Depth

Never rely on security rules alone:

javascript
// Client-side validation
if (title.length > 200) {
  throw new Error('Title too long');
}

// Security rules validation
allow write: if request.resource.data.title.size() <= 200;

// Cloud Function validation
if (!isValidTitle(data.title)) {
  throw new functions.https.HttpsError('invalid-argument');
}

Authentication Best Practices

Use Firebase Authentication

javascript
// Always verify authentication
allow read, write: if request.auth != null;

// Never trust client-provided UIDs
// Bad:
allow write: if request.resource.data.userId == userId;

// Good:
allow write: if request.auth.uid == userId;

Implement Custom Claims for Roles

javascript
// Set in Cloud Function
admin.auth().setCustomUserClaims(uid, { admin: true });

// Use in rules
allow write: if request.auth.token.admin == true;

Verify Email Before Access

javascript
allow read: if request.auth != null
  && request.auth.token.email_verified == true;

Data Validation Best Practices

Validate All Fields

javascript
allow create: if request.resource.data.keys().hasAll(['title', 'content', 'authorId', 'createdAt'])
  && request.resource.data.keys().hasOnly(['title', 'content', 'authorId', 'createdAt', 'tags'])
  && request.resource.data.title is string
  && request.resource.data.title.size() > 0
  && request.resource.data.title.size() <= 200
  && request.resource.data.content is string
  && request.resource.data.content.size() <= 10000
  && request.resource.data.authorId == request.auth.uid
  && request.resource.data.createdAt == request.time
  && (request.resource.data.tags == null || request.resource.data.tags is list);

Use Functions for Complex Validation

javascript
function isValidPost() {
  let post = request.resource.data;
  return post.keys().hasAll(['title', 'content'])
    && post.title is string
    && post.title.size() > 0
    && post.title.size() <= 200
    && post.content is string;
}

allow create: if isValidPost();

Protect Timestamps

javascript
// Force server timestamp
allow create: if request.resource.data.createdAt == request.time;

// Prevent timestamp modification
allow update: if request.resource.data.createdAt == resource.data.createdAt;

Query Security Best Practices

Restrict List Operations

javascript
// Don't allow unrestricted listing
// Bad:
allow list: if request.auth != null;

// Good: Restrict by query parameters
allow list: if request.auth != null
  && request.query.limit <= 20
  && request.query.orderBy == 'createdAt'
  && request.auth.uid in request.query.where[0].value;  // Only own data

Use Separate get/list Permissions

javascript
// Allow getting single documents
allow get: if request.auth.uid == userId;

// But deny listing all users
allow list: if false;

Storage Best Practices

User-Specific Paths

javascript
service firebase.storage {
  match /b/{bucket}/o {
    match /users/{userId}/{allPaths=**} {
      allow read, write: if request.auth.uid == userId;
    }
  }
}

Validate File Size and Type

javascript
allow write: if request.resource.size < 5 * 1024 * 1024  // 5MB
  && request.resource.contentType.matches('image/.*');

Scan for Malware

Use Cloud Functions:

javascript
exports.scanUpload = functions.storage.object().onFinalize(async (object) => {
  // Integrate with virus scanning service
  const isSafe = await scanFile(object.name);
  if (!isSafe) {
    await admin.storage().bucket().file(object.name).delete();
  }
});

Cloud Functions Best Practices

Always Verify Authentication

javascript
exports.sensitiveOperation = functions.https.onCall(async (data, context) => {
  // Verify authentication
  if (!context.auth) {
    throw new functions.https.HttpsError('unauthenticated');
  }

  // Verify authorization
  const userDoc = await admin.firestore().doc(`users/${context.auth.uid}`).get();
  if (!userDoc.data().admin) {
    throw new functions.https.HttpsError('permission-denied');
  }

  // Perform operation
});

Use CORS Properly

javascript
const cors = require('cors')({ origin: true });

exports.api = functions.https.onRequest((req, res) => {
  cors(req, res, () => {
    // Only allow specific origins in production
    if (process.env.NODE_ENV === 'production') {
      if (req.headers.origin !== 'https://your-domain.com') {
        res.status(403).send('Forbidden');
        return;
      }
    }

    // Handle request
  });
});

Input Validation

javascript
exports.createPost = functions.https.onCall(async (data, context) => {
  // Validate all inputs
  if (!data.title || typeof data.title !== 'string') {
    throw new functions.https.HttpsError('invalid-argument', 'Invalid title');
  }

  if (data.title.length > 200) {
    throw new functions.https.HttpsError('invalid-argument', 'Title too long');
  }

  // ... rest of function
});

Testing Best Practices

Automate Security Testing

yaml
# In CI/CD
- name: Security Scan
  run: firescan --config .firescan.yaml --json > results.json

- name: Fail on Critical Issues
  run: |
    if grep -q '"severity":"Critical"' results.json; then
      exit 1
    fi

Test All Authentication States

bash
# Unauthenticated
firescan > scan --unauth

# Authenticated normal user
firescan > auth --create-account
firescan > scan --all

# Admin user (if applicable)
firescan > auth --admin
firescan > scan --all

Regular Security Audits

Schedule monthly scans:

yaml
on:
  schedule:
    - cron: '0 2 1 * *'  # First of each month

Monitoring Best Practices

Enable Firebase Security Rules Monitoring

javascript
// Log rule evaluations in development
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if debug(request.auth != null);
    }
  }
}

Set Up Alerts

Monitor for:

  • Unusual access patterns
  • Failed authentication attempts
  • Excessive permission denials
  • Unexpected data modifications

Log Security Events

javascript
exports.onWrite = functions.firestore.document('users/{userId}').onWrite((change, context) => {
  // Log all user document modifications
  admin.firestore().collection('audit_log').add({
    collection: 'users',
    userId: context.params.userId,
    timestamp: admin.firestore.FieldValue.serverTimestamp(),
    before: change.before.data(),
    after: change.after.data()
  });
});

Common Anti-Patterns to Avoid

❌ Don't Trust Client Data

javascript
// Bad: Trusting client-provided role
allow write: if request.resource.data.role == 'user';

// Good: Use auth token
allow write: if request.auth.token.role == 'user';

❌ Don't Use Weak Checks

javascript
// Bad: Can be bypassed
allow write: if request.resource.data.userId != null;

// Good: Verify ownership
allow write: if request.resource.data.userId == request.auth.uid;

❌ Don't Ignore Edge Cases

javascript
// Bad: Doesn't handle updates
allow write: if request.auth != null;

// Good: Different rules for create/update
allow create: if request.auth != null;
allow update: if request.auth.uid == resource.data.authorId;

2026 Security Checklist

  • [ ] All rules default to deny
  • [ ] Authentication required for sensitive data
  • [ ] User isolation properly implemented
  • [ ] All writes validated (type, length, required fields)
  • [ ] Timestamps server-controlled
  • [ ] Critical fields protected from modification
  • [ ] Query parameters restricted
  • [ ] File uploads validated (size, type)
  • [ ] Cloud Functions authenticate and authorize
  • [ ] Automated security testing in CI/CD
  • [ ] Monthly security audits scheduled
  • [ ] Security monitoring and alerts enabled
  • [ ] Audit logs for sensitive operations
  • [ ] Regular updates to rules as app evolves

Next Steps

Stay ahead of security threats by following these best practices and testing regularly.


Need a professional security review? Get expert assessment or support this project.