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.