Skip to main content

Overview

Arre uses Firebase Security Rules to enforce data access control and validation. Rules ensure users can only access their own data and that all writes conform to the expected schema.
Security rules are your last line of defense. Never trust client-side validation alone.

Firestore Security Rules

The complete Firestore rules are defined in firestore.rules:

User Data Isolation

All user data is isolated under /users/{userId} with strict ownership checks:
firestore.rules:34-35
match /users/{userId} {
  allow read, write: if isOwner(userId);

Helper Functions

isOwner()

Verifies the authenticated user matches the resource owner:
firestore.rules:6-8
function isOwner(userId) {
  return request.auth != null && request.auth.uid == userId;
}
userId
string
required
The user ID from the document path to verify against request.auth.uid.

isValidProject()

Validates project document schema:
firestore.rules:11-17
function isValidProject() {
  let data = request.resource.data;
  let color = data.get('color', null);
  return data.title is string && 
         data.title.size() > 0 &&
         (color == null || color is string);
}
Validation Rules:
  • title must be a non-empty string
  • color must be a string if present

isValidTask()

Validates task document schema:
firestore.rules:20-31
function isValidTask() {
  let data = request.resource.data;
  let status = data.get('status', null);
  let projectId = data.get('projectId', null);
  
  return data.title is string && 
         data.title.size() > 0 &&
         status in ['todo', 'completed', 'canceled', 'someday'] &&
         (projectId == null || projectId is string);
}
Validation Rules:
  • title must be a non-empty string
  • status must be one of: 'todo', 'completed', 'canceled', 'someday'
  • projectId must be a string if present

Collection Rules

Projects Collection

firestore.rules:38-41
match /projects/{projectId} {
  allow read, delete: if isOwner(userId);
  allow create, update: if isOwner(userId) && isValidProject();
}
Permission: User must own the parent document
allow read, delete: if isOwner(userId);
No schema validation required for reads and deletes.

Tasks Collection

firestore.rules:44-47
match /tasks/{taskId} {
  allow read, delete: if isOwner(userId);
  allow create, update: if isOwner(userId) && isValidTask();
}
Permission: User must own the parent document
allow read, delete: if isOwner(userId);

Integrations Collection

firestore.rules:50-52
match /integrations/{integrationId} {
  allow read, write: if isOwner(userId);
}
No schema validation on integrations to allow flexibility for different OAuth providers. Handle validation in client code.

Storage Security Rules

Cloud Storage rules isolate user files by UID:
storage.rules:1-9
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /users/{userId}/{allPaths=**} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
  }
}

Path Structure

/users/{userId}/
  ├── uploads/
  ├── exports/
  └── {allPaths=**}
Access Rules:
  • Users can read/write ANY file under their own /users/{userId}/ path
  • No access to other users’ files
  • Authentication required for all operations
The {allPaths=**} wildcard matches all nested paths, allowing flexible file organization.

Testing Security Rules

Firestore Rules Testing

Use the Firebase Emulator Suite to test rules locally:
npm run emulators

Test Script Example

import { initializeTestEnvironment } from '@firebase/rules-unit-testing';

let testEnv;

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

it('should allow user to read their own tasks', async () => {
  const alice = testEnv.authenticatedContext('alice');
  const taskRef = alice.firestore().doc('users/alice/tasks/task1');
  
  await assertSucceeds(taskRef.get());
});

it('should deny reading other users tasks', async () => {
  const alice = testEnv.authenticatedContext('alice');
  const taskRef = alice.firestore().doc('users/bob/tasks/task1');
  
  await assertFails(taskRef.get());
});

Storage Rules Testing

import { initializeTestEnvironment } from '@firebase/rules-unit-testing';

it('should allow user to upload to their own folder', async () => {
  const alice = testEnv.authenticatedContext('alice');
  const fileRef = alice.storage().ref('users/alice/uploads/file.pdf');
  
  await assertSucceeds(fileRef.put(new Uint8Array([1, 2, 3])));
});

it('should deny uploading to other users folder', async () => {
  const alice = testEnv.authenticatedContext('alice');
  const fileRef = alice.storage().ref('users/bob/uploads/file.pdf');
  
  await assertFails(fileRef.put(new Uint8Array([1, 2, 3])));
});

Common Security Patterns

Authenticated User Check

if (request.auth != null) {
  // User is authenticated
}

Field Validation

let data = request.resource.data;

// Check field exists and is string
data.title is string

// Check string is non-empty
data.title.size() > 0

// Check value is in enum
data.status in ['todo', 'completed', 'canceled']

// Check optional field
data.get('projectId', null) == null || data.projectId is string

Ownership Verification

function isOwner(userId) {
  return request.auth != null && request.auth.uid == userId;
}

match /users/{userId}/tasks/{taskId} {
  allow read: if isOwner(userId);
}

Best Practices

1

Default Deny

Start with all access denied and explicitly allow specific operations:
match /databases/{database}/documents {
  // Default: all access denied unless explicitly allowed below
  match /users/{userId} {
    allow read: if isOwner(userId);
  }
}
2

Validate All Fields

Always validate type, format, and constraints for user-submitted data:
function isValidTask() {
  let data = request.resource.data;
  return data.title is string && 
         data.title.size() > 0 &&
         data.status in ['todo', 'completed', 'canceled', 'someday'];
}
3

Use Helper Functions

Extract common validation logic into reusable functions:
function isOwner(userId) {
  return request.auth != null && request.auth.uid == userId;
}
4

Test Thoroughly

Write unit tests for all security rules using the Firebase Emulator Suite.
5

Limit Data Exposure

Only allow access to fields that users genuinely need to read or modify.

Deployment

Deploy Firestore Rules

firebase deploy --only firestore:rules

Deploy Storage Rules

firebase deploy --only storage:rules

Deploy All Rules

firebase deploy --only firestore:rules,storage:rules
Rules take effect immediately after deployment. Test thoroughly in the emulator before deploying to production.

Debugging Rules

Check Rule Evaluation in Console

  1. Open Firebase Console → Firestore Database
  2. Click “Rules” tab
  3. Use “Rules Playground” to simulate requests

View Denied Requests

Security rule violations appear in Cloud Logging:
firebase functions:log --only firestore

Common Error Messages

Error: Missing or insufficient permissions
Cause: isOwner() check failed or user not authenticatedFix: Verify request.auth.uid matches the document owner

Next Steps

Firestore Schema

Understand the data structures being validated

Cloud Functions

Learn how functions interact with secured data

Build docs developers (and LLMs) love