Skip to content

Firebase Realtime Database Security Checklist

Use this comprehensive checklist to audit your Firebase Realtime Database security rules and ensure your data is properly protected.

Pre-Deployment Checklist

Authentication & Access Control

  • [ ] No world-readable paths - No .read: true in production rules
  • [ ] Authentication required - All sensitive paths require auth != null
  • [ ] User isolation - Users can only access their own data ($uid === auth.uid)
  • [ ] Role-based access - Admin/moderator roles properly implemented
  • [ ] Service account isolation - Backend services use separate authentication

Data Validation

  • [ ] Type validation - All writes validate data types
  • [ ] Required fields - Critical fields can't be null or missing
  • [ ] String length limits - All string fields have .length checks
  • [ ] Number ranges - Numeric values within acceptable bounds
  • [ ] Enum validation - Status fields only accept valid values

Write Protection

  • [ ] Immutable fields - CreatedAt, userId can't be modified
  • [ ] Ownership verification - Users can only modify their own content
  • [ ] Delete protection - Critical data can't be deleted by users
  • [ ] Cascading deletes - Rules handle related data cleanup properly
  • [ ] Rate limiting - Consider Firebase Functions for rate-limited writes

Query & Index Security

  • [ ] List operations restricted - Can't enumerate all users/documents
  • [ ] Index rules defined - .indexOn specified for queries
  • [ ] Deep path protection - Nested objects properly secured
  • [ ] Wildcard paths secured - All $variable paths have rules

Example Secure Rules

json
{
  "rules": {
    "users": {
      "$uid": {
        ".read": "$uid === auth.uid",
        ".write": "$uid === auth.uid && !data.exists()",
        "profile": {
          ".validate": "newData.hasChildren(['displayName', 'createdAt'])",
          "displayName": {
            ".validate": "newData.isString() && newData.val().length > 0 && newData.val().length <= 50"
          },
          "createdAt": {
            ".validate": "newData.isNumber() && (!data.exists() || data.val() === newData.val())"
          }
        }
      }
    },
    "posts": {
      "$postId": {
        ".read": "auth != null",
        ".write": "auth != null && (!data.exists() || data.child('authorId').val() === auth.uid)",
        ".validate": "newData.hasChildren(['title', 'content', 'authorId', 'createdAt'])",
        "authorId": {
          ".validate": "newData.val() === auth.uid && (!data.exists() || data.val() === newData.val())"
        }
      }
    }
  }
}

Testing Your Rules

Automated Testing

bash
# Install FireScan
go install github.com/JacobDavidAlcock/firescan/cmd/firescan@latest

# Test unauthenticated access
firescan > set projectID your-project
firescan > set apiKey AIza...
firescan > scan --unauth --rtdb

# Test authenticated access
firescan > auth --create-account
firescan > scan --rtdb -l all

Manual Testing

Use Firebase Emulator for local testing:

bash
firebase emulators:start
# Test at http://localhost:9000

Common Mistakes

  1. Forgetting .validate - Rules without validation accept any data
  2. Using only .write - Need both .write and .validate
  3. Not testing cascading rules - Parent rules override child rules
  4. Ignoring .indexOn - Queries fail without proper indexes
  5. Weak timestamp validation - Accepting client-provided timestamps

Security Best Practices

  • Start with deny-all, explicitly allow access
  • Test rules before deploying to production
  • Use Firebase Emulator for local development
  • Implement server-side validation in Cloud Functions
  • Monitor for suspicious activity in Firebase Console
  • Regular security audits with automated tools

Next Steps


Need help? Get professional security assessment or support FireScan.