Firebase hosting with the EmberFire adapter is an awesome way to quickly connect your Ember app to a fully functional backend. In a matter of minutes, you can set up your models and save and retrieve data. There are a few drawbacks though: the query language for retrieving data is a bit limited and may be unintuitive. Lets say we want load items from the database from a certain category. This what the store query would look like if you had SailsJS backend with blueprints active:

this.store.query('item', {category: 1})

With EmberFire same query looks like this:

this.store.query('item', {orderBy: 'category', startAt:1, endAt:1})

And what if you wanted to filter records by two parameters? With the current Firebase query language it is not possible. You can get pass that by creating special attributes that are combinations of both parameters:

this.store.query('item', {orderBy: 'categoryAndSection', startAt: '1-food', endAt: '1-food'})

But this is of course a bit inflexible and cumbersome to maintain. In practice, it is often just easier to filter by one parameter on the server and filter by second in the client. But that can often mean that you are loading much more data than you actually need.

It is a bit unfair to compare Firebase with Sails though. Sails is a framework that you set up on a server. Firebase is a service. It has to be performant.

Security rules

Securing your EmberFire application might not be easy. If you have just a few models, no authentication and no relationships, you’ll probably be fine and setting security rules will be a matter of few minutes, maybe an hour. But as your database becomes more complex, it is quickly getting harder and harder to make your back-end security hole free.

Please be aware that all the code snippets below are mainly for illustration purposes. Don’t forget to test your security rules as unauthenticated user and as various other users with the Firebase simulator tool!

Authorization and ownership

Lets say we have a simple article model. Only author of the article can save the model. We could start with something as simple as this:

“articles”: {

“$article_id”: {

“.write”: “newData.child(‘user’).val() === auth.uid”

}

}

This has one problem though. What if user is unauthenticated and passes null as a user? null === null would pass. That’s why we have to add:

"articles":{

“$article_id”: {

“.write”: “newData.child(‘user’).val() === auth.uid && auth

!== null”

}

}

This is still completely unsafe. Someone else could send a request and make himself owner of the article. Lets prevent that:

"articles": {

"$article_id": { //As long as there is no data stored in this data location,

logged user with with auth.uid can store auth.uid here //OR — if the user is author of the article he can change

whatever he wants except making someone else the author //OR — if the user is author of the article he can delete the

article ".write": "!data.exists() && newData.child('user').val() ===

auth.uid && auth !== null || data.child('user').val() ===

auth.uid && newData.child('user').val() === auth.uid ||

data.child('user').val() === auth.uid && newData.val() ===

null"

}

}

Finally this is safe. User can’t create article under someone else’s name. And only author of the article can edit it and delete it.

Relationships

For writing security rules for relationships it is important to understand the way relationships are stored.

Previous example will have to be redone if we have model that has an attribute that can be modified by anyone or a different user. It would be quite hard to modify the security rule above to allow different users modify just some specific attribute. Catching security holes in that could be a nightmare.

Let’s take the previous article model and incorporate recommends relationship into it. Let’s start with an article model with name, content, user and recommends attributes. Only author can edit the article but anyone can recommend it. Therefore any other logged user can write into /articles/$article_id/recommends/$recommend_id.

Maybe we could keep some of the rules in the /articles/$article_id and write additional “.write” rules in the /articles/$article_id/recommends? To go this way, we have to understand the top down principle. This means that if we try to write into /articles/$article_id/recommends the permission evaluation starts at /articles/ and evaluates the “.write” rule there, if it comes false, it moves in to the subsequent node and looks for “.write” rule there and so on. Any first rule that returns true stops the evaluation and the write is allowed.

Sometimes utilizing the top down principle can save you some duplicate code. But a security hole in a rule, that is high in the structure can be a problem. And then if you change that rule, you have to do quite a lot of testing or evaluation, that all the subsequent rules work as they should.

Therefore I prefer writing the rules as low in the structure as possible, usually for each model attribute separately, it might be some extra code, but then I can safely test the rules one by one. There is also the $other wildcard to quickly set “.write” rule to most of the attributes but in practice that works only as long as you don’t need to have “.validate” rules on the attributes. And most of the time you have to validate attributes differently. Using $other can also often mean that anyone using REST API passing the “.write” rule can store unlimited amount of data in the data node.

Lets get back to the article and recommendations model. Let’s write the rules separately for each attribute. We could start with something like this:

“articles”: {

“$article_id”: {

“user”: {

“.write”: “!data.exists() && newData.val() === auth.uid &&

auth !== null”

},

“name”: {

“.write”: “!data.exists() && auth !== null &&

newData.val()

!== null || data.parent().child(‘user’).val() === auth.uid

&& newData.val() !== null”,

“.validate”: “newData.val().length < 100”

},

“content”: {

“.write”: “!data.exists() && auth !== null &&

newData.val() !== null || data.parent.child(‘user’).val()

=== auth.uid && newData.val() !== null”,

“.validate”: “newData.val().length < 100 && newData.val()

!== null”

},

“recommends”: {

“$recommend_id”: {

“.write”: “!data.exists() && auth !== null ||

!newData.exists() && root.child(‘recommends/’ +

$recommend_id + ‘/user).val() === auth.uid”

}

}

}

}

This will work partially. It will let anyone create an article and only author of the article can later edit it. But it will still fail if some other user tries to recommend the article. Let me explain that later. First let’s go over the write rules in the code snippet above:

“name”: {

“.write”: “!data.exists() && auth !== null && newData.val()

!== null || data.parent(‘user’).val() === auth.uid &&

newData.val() !== null”

}

This means that any authenticated user can store anything in /articles/$article_id/name if there hasn’t been saved anything before. newData.val() !== null will make it a required attribute — even though this works only for the model.save() request. If someone does a REST API calls directly to /articles/$article_id/content and other paths can in fact change just specific attributes and article with no name can be created this way.

Afterwards, owner of the article can edit it but cannot delete it (newData.val() !== null).

“recommends”: {

“$recommend_id”: {

“.write”: !data.exists() && auth !== null ||

!newData.exists() && root.child(‘recommends/’ +

$recommend_id + ‘/user).val() === auth.uid

}

}

This allows any unauthenticated user add recommendations, but only owner can remove his recommendation.

But as was said before if User 2 tries to recommend articles from User 1, he will still receive a permission denied error. That’s becuase model.save() sends all the attributes to the server, not just those that change. This can be handled with: || newData.val() === data.val(). A little hack that allows to write values that actually don’t change anything.

Finally, the article security rules would look like this:

"articles": {

"$article_id": {

"user": {

".write": "!data.exists() && newData.val() === auth.uid && auth !== null || newData.val() === data.val()"

},

"name": {

".write": "!data.exists() && auth !== null && newData.val() !== null || data.parent().child('user').val() === auth.uid && newData.val() !== null || newData.val() === data.val()”,

".validate": “newData.val().length < 100"

},

"content": {

".write": "!data.exists() && auth !== null && newData.val() !== null || data.parent.child('user').val() === auth.uid && newData.val() !== null || newData.val() === data.val()",

".validate": "newData.val().length < 100 && newData.val() !== null"

},

"recommends": {

"$recommend_id": {

".write": "!data.exists() && auth !== null || !newData.exists() && root.child('recommends/' + $recommend_id + '/user').val() === auth.uid || newData.val() === data.val()"

}

}

}

}

Now only author can manipulate with his article, but anyone can recommend it. These security rules won’t allow the author to delete his article because /articles/$article_id/user and other attributes can’t be set to null. For delete functionality, we could instead set /articles/$article_id/trashed to true and then set:

“articles”:{

“.read”: “data.child(‘trashed’).val() !== true”

…

But then you have to make sure you are not trying to load anything with trashed true or you will get a permission error. As Firebase security docs state: rules are not filters.

Maybe you have caught one security hole in the recommendations security rules: user can store as many recommendations as he wants.

Uniqueness

Handling uniqueness with Firebase is not easy, at least in a way that can’t be bypassed. First step is to prevent user to set duplicate relationships on the client side. Now comes the hard part — the server side. We could alter the path of recommendations relationship from /articles/$article_id/recommendations/$recommendation_id to /articles/$article_id/recommendations/$user_id/$recommendation_id

This structure could be protected with “.write” rule like this:

"$article_id": {

"recommendations": {

"$user_id": {

".write”: "!data.exists() && auth !== null || $user_id ===

auth.uid && newData.val() === null"

}

}

}

This would work. But the ember data adapter of course does not understand this structure and you would have to develop your own way of loading recommendations and setting up relationships.

Alternative options include:

1 ) Relying on Ember.computed.uniq to remove duplicate recommendations. This is actually quite safe to use, unless you have really lot of “article snippets” and the uniq function has to be executed a lot.

2) Seting up a CRON job that checks the data and cleans duplicates. This is good in combination with 1) as it also prevents the Firebase to be flooded with data. But I understand that CRON can be something you don’t want to deal with if you chose Firebase as your backend.

3) Add a middleware a server that will check if recommendations can be added and add it afterwards. Only this server would have a right to manipulate with recommendations — by sending some secret token or authenticating as a specific user. This might make the reccomendation action take longer and again, you may not want to spend time and resources setting up one more backend service.

Counting children

Lets continue with the article and recommendation example. Medium allows you to add three tags to an article. No more. How could we write security rule for this in Firebase?

There is no method in the security API that allow us to count children. What we can do, is to set up a counter. User could send a request and not increase the counter though. We could write a rule for it like so:

".write": "!data.exists() newData.child('counter').val() === data.child('counter') + 1"

This would still allow the user to post a request with multiple records at once while increasing counter just by one. To prevent this, we have to once again change the structure of data. Basically we have to wrap each child within to a data node with a numerical id.

Altered structure to store relationships

Actually, with a structure like that and with limit of 3. Easiest solution would be:

"tags": {

"$tag_n": {

".write": "!data.exists() && ($tag_n === '3' || $tag_n === '2' || $tag_n === '1')"

}

}

For higher numbers we have to utilize the counter:

"counter":{

".write": "newData.val() === data.val() || newData.val() === data.val() + 1 && newData.val() <= 3"

},

"tags": {

"$tag_n": {

".write": "$tag_n === ''+ data.parent().parent().child('counter').val()"

}

}

ID’s are interpreted as strings (even if they are numbers) so we have to convert the counter value into a string as well with:

'' + data.parent().parent().child('counter').val()

This solution is reliable. But again, to utilise this you will have to develop a custom way to save and load records from Firebase.

There you have it. Writing security rules for Firebase can be tricky but most of the time you can achieve what you need. For some solutions you need to alter the data structure and you will have to come up with a custom way to store and load data from Firebase.

— — — — —

I am a Freelancer Developer currently looking for work for upcoming months. I specialise in building single page applications with Ember.js and performance optimisations. Feel free to contact me at malindacz@gmail.com or twitter.com/@martinmalinda.cz