Proper authorization rules for protected content in firebase

abaker picture abaker · Jul 23, 2013 · Viewed 10k times · Source

Is there a best practice approach to proper authorization rules for protected content in a firebase app

  • using firepad specifically
  • By protected content I mean where a user creates a document and only shares it with certain other users).
  • Also I need to be able to query firebase for all documents that i have access to (docs that I created and docs other users shared with me)

Some of my research thus far:

Method 1: Secret URL

  • I need to know the URL to be able to view/edit the document

  • Not real authorization, as any logged in user that has access to that URL could edit/modify it.

  • Cant index all the docs i have access to

Method 2: Using firebase authorization rules to add users to a document and check if user is document.users before reading/writing.

Taken From: Protected content in Firebase possible?

{

"documents": {

   "$documents_id": {

       // any friend can read my post

       ".read":  "auth.id === data.child('owner').val() || root.child('users/'+data.child.owner.val()+'/users/'+auth.id).exists()",

       // any friend can edit my post
       ".write": "auth.id === data.child('owner').val() || root.child('users/'+data.child.owner.val()+'/users/'+auth.id).exists()"

   },

   users:{

   // List of user.ids that have access to this document

   }

}

}

Pros:

  • Proper authorization/authentication. Only authenticated users who have been granted access can view/edit.

Cons:

  • Cannot query for all documents a user is allowed to edit (those that I own or have been shared with me) (Is this assumption correct?)

Method 3: Firebase authorization rules (method 2), plus a redundant store of users with array of document_ids each users has access to. This user store would only be used to query all the documents a user has access to. ie:

{
"documents": {
   "$documents_id": {
       // any friend can read my post
       ".read":  "auth.id === data.child('owner').val() || root.child('users/'+data.child.owner.val()+'/users/'+auth.id).exists()",
       // any friend can edit my post
       ".write": "auth.id === data.child('owner').val() || root.child('users/'+data.child.owner.val()+'/users/'+auth.id).exists()"
   }
},
"users":{
    "$user":{
        ".read": "auth.id=$user.id",
        ".write": "auth.id=$user.id"
        "$documents":{
            // All the documents i have access to. This list gets ammended whenever I am granted/stripped access to a document.
        }
    }
}
}

Pros:

  • Proper authentication/authorization

Cons:

  • Duplicate data, have to deal with synchronization issues between two data stores. This just doesnt seem like a good idea.

Method 4: Groups

Using groups per Granting access to Firebase locations to a group of users

  • We have a group for every document in data store

  • Cant easily query firebase for all the docs a user can access

Is there a better way to do this?

Answer

Michael Lehenbauer picture Michael Lehenbauer · Jul 23, 2013

You've done a good job of enumerating the options, and you're definitely on the right track. As you've discovered, there's no way to query based on security rules. This was done intentionally, since (depending on your security rules) this could be quite expensive (Firebase avoids complex queries in general for this reason).

So your method 3 is the exact right way to do this. Duplicating data for these sorts of situations is actually a very common practice. See Denormalizing Your Data is Normal for a blog post that goes into more detail on this.

You could also do method 1 with the duplicated document list. This is especially useful if you want to be able to "invite" somebody to a document just with a URL (that contains the secret ID). Or you could do a combination of the two (have some documents be "public but unlisted" and some be "private to invited friends" or whatever.)