Skip to content
Firestore Security Rules logo

Firestore Security Rules

Write and validate Firestore Security Rules following the project's multi-tenancy Blueprint pattern. Use this skill when implementing collection-level security, Blueprint membership validation, role-based permissions, and data access controls. Ensures rules validate BlueprintMember status, check ...

SKILL.md

Full skill instructions

Firestore Security Rules Skill

This skill helps create secure Firestore Security Rules following the project's multi-tenancy architecture.

Core Principles

Security-First Architecture

  • Security Rules are First Defense: Never trust client-side checks alone
  • Multi-Tenancy: Blueprint-based data isolation
  • Permission System: Role + permissions array validation
  • Server-Side Validation: All access validated at database level

Blueprint Multi-Tenancy Model

User → Organization → Blueprint → Resources
           ↓
      Team / Partner
  • Blueprint defines permission boundaries (not data boundaries)
  • BlueprintMember is dedicated collection for membership
  • All resources belong to a Blueprint
  • Access controlled via BlueprintMember role + permissions

Core Helper Functions

Global Authentication

rules_version = '2';
service cloud.firestore {
  match /​databases/​{database}/​documents {
    
    /​**
     * Check if user is authenticated
     */
    function isAuthenticated() {
      return request.auth != null;
    }
    
    /​**
     * Get current user ID
     */
    function getCurrentUserId() {
      return request.auth.uid;
    }
    
    /​**
     * Check if user is system admin
     */
    function isSystemAdmin() {
      return isAuthenticated() && 
             get(/​databases/​$(database)/​documents/​users/​$(getCurrentUserId())).data.role == 'admin';
    }
  }
}

Blueprint Membership Functions

/​**
 * Check if user is Blueprint member
 */
function isBlueprintMember(blueprintId) {
  let memberId = getCurrentUserId() + '_' + blueprintId;
  return exists(/​databases/​$(database)/​documents/​blueprintMembers/​$(memberId));
}

/​**
 * Get user's role in Blueprint
 */
function getBlueprintMemberRole(blueprintId) {
  let memberId = getCurrentUserId() + '_' + blueprintId;
  let member = get(/​databases/​$(database)/​documents/​blueprintMembers/​$(memberId));
  return member.data.role;
}

/​**
 * Check if user is Owner or Admin
 */
function isBlueprintOwnerOrAdmin(blueprintId) {
  let role = getBlueprintMemberRole(blueprintId);
  return role in ['owner', 'admin'];
}

/​**
 * Check if user has specific permission
 */
function hasPermission(blueprintId, permission) {
  let memberId = getCurrentUserId() + '_' + blueprintId;
  let member = get(/​databases/​$(database)/​documents/​blueprintMembers/​$(memberId));
  return permission in member.data.permissions;
}

/​**
 * Check if member is active
 */
function isMemberActive(blueprintId) {
  let memberId = getCurrentUserId() + '_' + blueprintId;
  let member = get(/​databases/​$(database)/​documents/​blueprintMembers/​$(memberId));
  return member.data.status == 'active';
}

Data Validation Functions

/​**
 * Validate task data structure
 */
function validateTaskData(data) {
  return data.keys().hasAll(['blueprintId', 'title', 'status']) &&
         data.title is string &&
         data.title.size() > 0 &&
         data.title.size() <= 200 &&
         data.status in ['pending', 'in-progress', 'completed', 'archived'];
}

Collection Rules Pattern

Resource Collection (with Blueprint)

match /​tasks/​{taskId} {
  // Blueprint members can read tasks
  allow read: if isAuthenticated() && 
                 isBlueprintMember(resource.data.blueprintId) &&
                 isMemberActive(resource.data.blueprintId);
  
  // Members with task:create permission can create
  allow create: if isAuthenticated() && 
                   isBlueprintMember(request.resource.data.blueprintId) &&
                   isMemberActive(request.resource.data.blueprintId) &&
                   hasPermission(request.resource.data.blueprintId, 'task:create') &&
                   validateTaskData(request.resource.data);
  
  // Members with task:update permission or assignee can update
  allow update: if isAuthenticated() && 
                   isBlueprintMember(resource.data.blueprintId) &&
                   isMemberActive(resource.data.blueprintId) &&
                   (hasPermission(resource.data.blueprintId, 'task:update') || 
                    resource.data.assignedTo == getCurrentUserId()) &&
                   validateTaskData(request.resource.data);
  
  // Only members with task:delete permission can delete
  allow delete: if isAuthenticated() && 
                   isBlueprintMember(resource.data.blueprintId) &&
                   isMemberActive(resource.data.blueprintId) &&
                   hasPermission(resource.data.blueprintId, 'task:delete');
}

Nested Subcollections

match /​blueprints/​{blueprintId} {
  allow read: if isAuthenticated() && isBlueprintMember(blueprintId);
  
  // Nested tasks subcollection
  match /​tasks/​{taskId} {
    allow read: if isAuthenticated() && 
                   isBlueprintMember(blueprintId) &&
                   isMemberActive(blueprintId);
    
    allow create: if isAuthenticated() && 
                     isBlueprintMember(blueprintId) &&
                     isMemberActive(blueprintId) &&
                     hasPermission(blueprintId, 'task:create') &&
                     validateTaskData(request.resource.data) &&
                     request.resource.data.blueprintId == blueprintId;
  }
}

Membership Collection

match /​blueprintMembers/​{memberId} {
  // Blueprint members can read member list
  allow read: if isAuthenticated() && 
                 isBlueprintMember(resource.data.blueprintId);
  
  // Only Owner/​Admin can add/​modify/​remove members
  allow create, update: if isAuthenticated() && 
                           isBlueprintOwnerOrAdmin(resource.data.blueprintId);
  
  // Only Owner can delete members (soft delete recommended)
  allow delete: if isAuthenticated() && 
                   isBlueprintOwnerOrAdmin(resource.data.blueprintId);
}

User Collection

match /​users/​{userId} {
  // Users can only read their own data
  allow read: if isAuthenticated() && getCurrentUserId() == userId;
  
  // Users can update their own data (but not role)
  allow update: if isAuthenticated() && 
                   getCurrentUserId() == userId &&
                   !request.resource.data.diff(resource.data).affectedKeys().hasAny(['role']);
  
  // Only system admin can create/​delete users
  allow create, delete: if isSystemAdmin();
}

Audit Logs (Write-Only)

match /​auditLogs/​{logId} {
  // Only system admin can read logs
  allow read: if isSystemAdmin();
  
  // Any authenticated user can write logs
  allow create: if isAuthenticated();
  
  // Logs cannot be modified or deleted
  allow update, delete: if false;
}

Performance Optimization

Minimize get() Calls

// ❌ BAD: Multiple get() calls
function hasMultiplePermissions(blueprintId) {
  let memberId = getCurrentUserId() + '_' + blueprintId;
  let member = get(/​databases/​$(database)/​documents/​blueprintMembers/​$(memberId));
  let canCreate = 'task:create' in member.data.permissions;
  let member2 = get(/​databases/​$(database)/​documents/​blueprintMembers/​$(memberId)); // Duplicate!
  let canUpdate = 'task:update' in member2.data.permissions;
  return canCreate && canUpdate;
}

// ✅ GOOD: Single get() call
function hasMultiplePermissions(blueprintId) {
  let memberId = getCurrentUserId() + '_' + blueprintId;
  let member = get(/​databases/​$(database)/​documents/​blueprintMembers/​$(memberId));
  return 'task:create' in member.data.permissions && 
         'task:update' in member.data.permissions;
}

Use exists() When Possible

// ✅ GOOD: exists() is faster than get()
function isBlueprintMember(blueprintId) {
  let memberId = getCurrentUserId() + '_' + blueprintId;
  return exists(/​databases/​$(database)/​documents/​blueprintMembers/​$(memberId));
}

// Only use get() when you need data
function getBlueprintMemberRole(blueprintId) {
  let memberId = getCurrentUserId() + '_' + blueprintId;
  let member = get(/​databases/​$(database)/​documents/​blueprintMembers/​$(memberId));
  return member.data.role;
}

Testing Security Rules

Using Firebase Emulator

# Start emulator
firebase emulators:start

# Run security rules tests
npm run test:rules

Test Template

describe('Task Collection Security Rules', () => {
  it('should allow authenticated blueprint member to read tasks', async () => {
    const db = testEnv.authenticatedContext('user1').firestore();
    
    // Setup: Create member
    await testEnv.withSecurityRulesDisabled(async (context) => {
      await context.firestore().doc('blueprintMembers/​user1_blueprint1').set({
        blueprintId: 'blueprint1',
        userId: 'user1',
        role: 'member',
        status: 'active',
        permissions: ['task:read']
      });
    });
    
    // Test: Read should succeed
    await assertSucceeds(db.doc('tasks/​task1').get());
  });
  
  it('should deny unauthenticated access', async () => {
    const db = testEnv.unauthenticatedContext().firestore();
    await assertFails(db.doc('tasks/​task1').get());
  });
});

Checklist

When writing Security Rules:

  • All collections have security rules
  • Multi-tenancy data isolation implemented
  • BlueprintMember membership checked
  • Permissions array validated
  • Member status checked (active/​suspended)
  • Data validation functions complete
  • Minimize get() calls for performance
  • Use exists() when only checking existence
  • Audit logs are write-only
  • Cross-blueprint access prevented
  • Tests cover all scenarios
  • Both positive and negative tests

Integration with Architecture

Three-Layer Security

1. UI Layer: Permission checks for UX (can hide buttons)
2. Service Layer: Business logic validation
3. Security Rules: Server-side enforcement (MUST)

Repository + Security Rules

// Repository - No permission checks
async deleteTask(taskId: string): Promise<void> {
  await this.taskRepository.delete(taskId);
  // Security Rules will validate permissions
}
// Security Rules - Permission enforcement
match /​tasks/​{taskId} {
  allow delete: if isAuthenticated() 
                && isBlueprintMember(resource.data.blueprintId)
                && hasPermission(resource.data.blueprintId, 'task:delete');
}

References

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View