Firestore Security Rules

March 6, 2019

Firestore Security Rules is one of the most important aspect of Firestore usage:

  • Security Rules only apply to mobile and web client libraries, doesn’t apply to server client libraries. Technically, user could manipulate mobile/web client, but not server code.
  • If you don’t design the security rules properly, user can create/update/delete any data/documents. It is like configuring the security/permission of REST APIs.

Be Restrictive

Make sure all documents don’t allow read/write by default.

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if false;
    }
  }
}  

Even if you want to do testing, only assign permission per collection when necessary.

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if false;
    }
    
    match /tests/{testId} {
      allow read: if true
      // must sigin
      allow write: if request.auth != null;
    }
  }
}

Check is Signin, is User, is Owner, is Admin

Check is Signin/Login.

match /tests/{testId} {
  function isLogin() {
    return request.auth != null
  }

  allow read: if true
  // must sigin
  allow write: if isLogin()
}

NOTE: request.auth is populated if client use Firebase Authentication.

Check is valid User.

function isUser() {
  return isLogin() && exists(/databases/$(database)/documents/users/$(request.auth.uid))
}

NOTE: Assume we have a collection users to store all valid user.

Check if valid User is active.

function isUserActive() {
  return isLogin() && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.active == true
}

NOTE: Assume the field/property active is used in User document to indicate active or not.

Check if User is Admin

  • We can hardcode uid as Admin
  • or use field/property roles (an array) in User document (or you can use admin == true)
function isAdmin() {
  return isLogin() && 
    (request.auth.uid == 'QYHqCKCGSbY3qqMSliqNhR3j9QC2' ||
     'admin' in get(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles)
}

NOTE: Refer to Firestore Check Is Admin Security Rules for more alternatives.

Check is Owner

For users collection, where {userId} is the Document ID and could be used to check against request.auth.uid.

match /users/{userId} {
  function isOwner() {
    return userId == request.auth.uid;
  }
}

For other collection, we would need to check against the User ID property/field of the document.

match /comments/{commentId} {
  function isOwner() {
    return request.resource.data.userId == request.auth.uid;
  }
}

NOTE: The above check can prevent user from imitating another user, check checking resource.data.userId only doesn’t.

Test Security Rules

Simulator

You can use the Simulator at Firebase Console -> Database -> Rules -> Simulator.

NOTE: Service call error. Function: [exists], Argument ... may happend if you enable Authenticated without Firebase UID while using rules like exists(/databases/$(database)/documents/users/$(request.auth.uid))

NOTE: I bump into error Function not found error: Name: [get] when using get(/databases/$(database)/documents/users/$(request.auth.uid)) while using the Simulator, and it works fine in production. This is probably a temporary bug.

Cloud Firestore Emulator

You can use Cloud Firestore Emulator.

Use Android Unit Test

Firestore Test Production Security Rules With Android Unit Test

References:

This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.