How to Write Firebase Auth Rules

Share
How-To Guide

How to Write Firebase Auth Rules

Protect your Firebase data with authentication-based security rules

TL;DR

TL;DR (20 minutes): Use request.auth != null to require authentication. Use request.auth.uid == resource.data.userId to ensure users only access their own data. Store user roles in custom claims or a separate collection, then check them in rules.

Prerequisites:

  • Firebase project with Firestore or Realtime Database
  • Firebase Authentication set up
  • Basic understanding of Firebase security rules syntax

Why This Matters

Firebase security rules are your last line of defense. Even if your frontend code is "secure," anyone can call your Firebase APIs directly. Without proper auth rules, attackers can read all your data, modify other users' records, or delete everything.

The most common vulnerability in vibe-coded Firebase apps is leaving rules in "test mode" - wide open to the public. This guide shows you how to lock things down properly.

Step-by-Step Guide

1

Understand the request.auth object

When a user is authenticated, request.auth contains their info:

// request.auth structure
{
  uid: "user123",           // Unique user ID
  token: {
    email: "user@example.com",
    email_verified: true,
    name: "John Doe",
    // Custom claims you've set
    admin: true,
    role: "premium"
  }
}

If the user is not authenticated, request.auth is null.

2

Require authentication for all access

Start by locking down everything to authenticated users only:

// Firestore rules
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Default: deny all
    match /{document=**} {
      allow read, write: if false;
    }

    // Require authentication for specific collections
    match /posts/{postId} {
      allow read: if request.auth != null;
      allow write: if request.auth != null;
    }
  }
}
3

Implement user-owned data rules

Ensure users can only access their own data:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // User profiles - users can only read/write their own
    match /users/{userId} {
      allow read, write: if request.auth != null
                         && request.auth.uid == userId;
    }

    // User's private data
    match /users/{userId}/private/{document} {
      allow read, write: if request.auth != null
                         && request.auth.uid == userId;
    }

    // Orders - users can only see their own orders
    match /orders/{orderId} {
      allow read: if request.auth != null
                  && request.auth.uid == resource.data.userId;
      allow create: if request.auth != null
                    && request.auth.uid == request.resource.data.userId;
    }
  }
}
4

Add role-based access with custom claims

First, set custom claims in your backend:

// Node.js Admin SDK - set user as admin
const admin = require('firebase-admin');

async function setAdminRole(uid) {
  await admin.auth().setCustomUserClaims(uid, { admin: true });
}

// Set premium role
async function setPremiumRole(uid) {
  await admin.auth().setCustomUserClaims(uid, { role: 'premium' });
}

Then check claims in your rules:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Admin-only collection
    match /admin/{document=**} {
      allow read, write: if request.auth != null
                         && request.auth.token.admin == true;
    }

    // Premium content
    match /premium-content/{docId} {
      allow read: if request.auth != null
                  && request.auth.token.role == 'premium';
    }

    // Public content anyone authenticated can read
    match /public/{docId} {
      allow read: if request.auth != null;
      allow write: if request.auth != null
                   && request.auth.token.admin == true;
    }
  }
}
5

Alternative: Store roles in Firestore

If you can't use custom claims, store roles in a collection:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Helper function to check admin status
    function isAdmin() {
      return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == 'admin';
    }

    // Helper function to check any role
    function hasRole(role) {
      return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == role;
    }

    match /admin-settings/{docId} {
      allow read, write: if request.auth != null && isAdmin();
    }

    match /premium/{docId} {
      allow read: if request.auth != null && hasRole('premium');
    }
  }
}
6

Test your rules with the Emulator

Use the Firebase Emulator Suite to test rules locally:

# Install and start emulators
firebase emulators:start

# Run rules tests
firebase emulators:exec "npm test"

Write unit tests for your rules:

// rules.test.js
const { assertSucceeds, assertFails } = require('@firebase/rules-unit-testing');

describe('Firestore rules', () => {
  it('allows users to read their own data', async () => {
    const db = getFirestore({ uid: 'user123' });
    await assertSucceeds(db.collection('users').doc('user123').get());
  });

  it('denies users from reading other users data', async () => {
    const db = getFirestore({ uid: 'user123' });
    await assertFails(db.collection('users').doc('other-user').get());
  });

  it('denies unauthenticated access', async () => {
    const db = getFirestore(null); // No auth
    await assertFails(db.collection('users').doc('anyone').get());
  });
});

Common Mistakes to Avoid:

  • Never leave rules in test mode (allow read, write: if true) in production
  • Don't trust client-provided role data - always verify on the server or in rules
  • Remember: get() in rules counts as a read operation and affects billing
  • Custom claims are cached - users may need to sign out and back in to see changes

How to Verify It Worked

  1. Firebase Console: Go to Firestore → Rules → Run a simulation
  2. Try unauthorized access: Use an incognito browser to test unauthenticated requests
  3. Test cross-user access: Log in as User A and try to access User B's data
  4. Check the Rules Playground: Simulate different auth states in Firebase Console

Common Errors & Troubleshooting

Error: "Missing or insufficient permissions"

Your rules are denying the request. Check that the user is authenticated and has the required role/ownership.

Custom claims not working

Claims are cached in the ID token. Have the user sign out and back in, or force a token refresh with user.getIdToken(true).

Rules working locally but failing in production

Make sure you deployed your rules: firebase deploy --only firestore:rules

get() function returning null

The document doesn't exist. Add a null check: exists(/path/to/doc) && get(...).data.field

Custom claims vs. Firestore roles collection - which is better?

Custom claims are faster (no extra reads) and more secure (can't be modified by clients). Use them for simple roles. Use a Firestore collection when roles are complex, frequently changed, or need to store additional metadata.

How do I handle public data that anyone can read?

Create a separate collection for public data with allow read: if true but still restrict writes to authenticated users or admins.

Can I use Firebase Rules with anonymous authentication?

Yes, anonymous users still have a request.auth.uid. You can allow limited access to anonymous users while requiring full authentication for sensitive operations.

Related guides:Firebase Security Rules · Firebase Auth Setup · Supabase RLS Setup

How-To Guides

How to Write Firebase Auth Rules