I’m trying to create security rules for the “Save data as transactions” blogging app example from the Firebase guide.
The user can increase or decrease the star count for a post, having his own UID being included or removed from the node at the same time.
I’ve written the following rules:
(I removed the rules for the counter increase/decrease since they are out of the scope of the question)
“stars”: {
".read": true,
"$postId”: {
".write": "auth != null && (newData.child('users').child(auth.uid).exists() || data.child('users').child(auth.uid).exists())",
"users": {
"$userId": {
".validate": "$userId === auth.uid"
}
}
}
}
And an exemple of a stars node:
“stars”: {
“postId1”: {
starCount: 2,
"users": {
“userId1”: true,
“userId2”: true
}
}
}
The rules work fine for adding an user to the “users” node, but a problem arises when removing.
It’s possible for a mean-spirited user to remove any other user from the “users” node, just update it with a empty node. Or a node with all the users from before, minus one he chose to remove.
The “.validate” rule ("$userId === auth.uid") does not work for a empty node being submited and I can't write a rule that checks if all the users that were in the database before the update are still there after.
The way I’d solve the problem if I wasn’t using transactions was to to move the “.write” rule to under “$userId”, limiting the uptate for only one user at a time and only with the same UID as the logged user.
Something like:
“stars”: {
".read": true,
"$postId”: {
"users": {
"$userId": {
".write": "auth != null && $userId === auth.uid"
}
}
}
"starCount": {
".write": true
}
But since I’m doing the database update using transactions I need the “.write” rule under the "$postId”, permitting the update of the “users” node and the “starCount” node at the same time. Something that would not be possible in my last exemple (no “.write” rule under "$postId”).
So it seem like a Catch-22. Or I use transactions but I’m not able to secure the starCount with rules, or I do it as a normal multi-update but loose the concurrency benefits for increasing the counter.
How can I correctly secure the “Save data as transactions” blogging app exemple?
Related
I have a firebase realtime database that stores users information. I am creating a dashboard where I can track all of this , but I am having trouble with creating secure rules. I want to be able to read and write on this dashboard but users cannot read the database. I will be the only person on the dashboard since its local. I was thinking of like checking for an api key that I can have in the dashboard but I cannot find any information online. If you have any suggestions please let me know. These are my current rules below. I have it to where nobody can read the database but they can write to it. I want to be able to read the database from the dashboard.
{
"rules": {
".read": "false",
".write": true,
"posts": {
"$uid": {
".write": "!data.exists()"
}
}
}
}
The security rules will depend on what exactly it is that the users can read/write but if you wanted to be the only person who can do either you could set the security rules to something like,
{
"rules": {
"users": {
"$node_with_data": {
".write": "auth.uid === 'enter_your_uid_here_123' ",
".read": "auth.uid === 'enter_your_uid_here_123' "
}
}
}
}
This means that the only person who can read/write to the node specified is the the user with the uid that matches the one you enter. Obviously this would mean the users couldn't write to this node so you'll need to think about what types of users you have and what they're allowed to access.
Here's a useful link Firebase Security Rules
What I did is use firebase Authentication and what I can do is allow only authenticated users through since I don't authenticate anyone or I can only let my Gmail through which works great! Mine looks like this
{
"rules": {
".read": "auth.token.email_verified == true && auth.token.email.matches(/YOUROWNEMAIL/)",
".write": true,
"posts": {
"$uid": {
".write": "!data.exists()" // This makes it so nobody can delete anything
}
}
}
}
Is there a way to check the values using rules that are initially set on user creation?
I have perused the firebase docs to no avail.
When the function createUserWithEmailAndPassword is called, I then create some values in the database, for example:
"users": {
"ht35resf435dwe3rfdw": {
"is_premium": false,
"display_name" "John",
"last_login": 15353723826
}
}
The problem I am facing is: is_premium: false is part of the front end code and I am worried that a user could somehow change this to is_premium: true.
I can't figure out a way to check that it is initially set to false on creation.
P.s I could be going about this all wrong, I am a junior so I would appreciate any and all pointers.
To only allow a value to be set to false, you can use a validation rule:
{
"rules": {
"users": {
"$uid": {
"is_premium": {
".validate": "newData.isBoolean() && newData.val() == false"
}
}
}
}
}
The above will simply only allow false to be written by any client. When you're writing using an Admin SDK however, those writes bypass these security rules. So you can use the Admin SDK to mark premium users.
you can just change the security rules as below so that the user won't be able to make changes.
{
"rules": {
"foo": {
".read": true,
".write": false
}
}
}
I just started using Firebase so I cannot provide much information. If you want to start learning it there is a great series you can watch. Also, I would suggest using Cloud Firestore instead of the Realtime Database because the ladder is older and has fewer features. Changing the security rules on your database will let you fiddle with which users can edit data. What you need to do is make the premium default to false or even not exist in the database and then create a javascript function to create or change it to true in the database.
EDIT:
Ajith Naruto's would work but it would also disallow all writing to the database from the web app.
EDIT 2:
Frank van Puffelen's should work I would go off of his answer.
I'm building an HTML/JavaScript/CSS front-end application using only Firebase as my backend (and I'd like to keep it that way, but I'm not sure if what I want to do is possible without another server).
I'm trying to implement a following/followers functionality. I have a node in the Firebase database that is basically a list of users. The object key is the user's auth uid, and the value is an object describing properties of that user object.
I want to have a "follow" button in my app that, when clicked, adds an object with the uid of the user being followed to the "following" object of the current user. Also, I want to add an object with the current user's uid to the "followers" node of the user being followed.
followers node:
following node:
The trouble I'm having is that Firebase recommends to set up their security rules something like this:
{
"rules": {
"users": {
"$uid": {
".write": "$uid === auth.uid"
}
}
}
}
This way it's fine to expose your firebase key because any given authenticated user can only write to his own object in the database. So my question is: What's the best way for me to allow one user to modify another's database object (by writing to the other user's "followers" node) while still preventing anyone with my Firebase key from maliciously writing to any/every user's object?
Since firebase dataBase grantings overwrite higher level revocations, you can just leave the rule as it is and grant write permission to all users to the followers node of each user:
{
"rules": {
"users": {
"$uid": {
".write": "$uid === auth.uid"
"followers": {
".write": auth != null
}
}
}
}
You can also add a verification to the node written to followers such as !data.exists() && newData.child("uid").val() === auth.uidto prevent users from deleting/modifying other user's followers and prevent adding random uid's as followers
Edit:
Let's imagine your database structure is the folowing:
{
users: {
uid: {
(user data)
followers: {
uid : timestamp
}
}
}
}
Then the rules would be:
{
"rules": {
"users": {
"$uid": {
".write": "$uid === auth.uid",
"followers": {
"$follower": {
".write": "auth != null && !data.exists() && newData.key() === auth.uid"
}
}
That way, one user can only write new entries in the other users' followers node and that entry's key must be his uid.
}
}
When I login with simple login and use authData.uid I get the return of simplelogin:1 (with 1 being the id of my user.
My user structure is set up like this:
{
"users":
{
"1": { ... },
"2": { ... }
}
}
And my rules are set up like this:
{
"rules": {
"users": {
"$userid": {
".read": "auth.uid == $userid",
".write": "auth.uid == $userid"
}
}
}
}
So I am a little confused on how to make it so /users/1 would work. Does everyone normally make there user structure "simplelogin:1" instead of "1"?
It's saying I don't have permission to view my user since my $userid is "simplelogin:1" and not "1"
Does anyone know how I can fix this?
In your example above, the unique, cross-provider user id is simplelogin:1, not just 1. Using only the integer is problematic, because these user ids are not unique across all providers (Twitter, Facebook, Google, GitHub, etc.) and may have collisions.
It is expected and by-design that the user ids include the prefix for how the user was authenticated. Also note that the format of these user ids may change in the future, so it is not recommended that you attempt to parse them or treat them as human-readable. They are only intended to be unique alphanumeric ids for your Firebase.
Let's say I have a collection of articles:
https://example.firebaseio.com/articles/$key
And I want to add a viewCounter and currentUsersCounter children to articles.
https://example.firebaseio.com/articles/$key/viewCounter
https://example.firebaseio.com/articles/$key/currentUsersCounter
I execute a function anytime a user has scrolled into the next article:
var currentArticle = function(key){
//key == index of the article
//This is where I want to increment the `viewCounter` and `currentUsersCounter`
}
Obviously I don't want anyone writing to anything more then those two children.
How do I expand on my security rules which currently (black-lists all writes) to white-listing writes only for these specific collections?
How would I limit writes to unique IP addresses in my security rules for these white-listed collections? (if possible)
Currently black-listing all writes:
{
"rules": {
".read": true,
".write": "auth.email == 'example#gmail.com'"
}
}
You can't do this in a way that will protect the integrity of the data (i.e. ensuring that the counts are actually accurate), since if you grant write access to those fields a client can write whatever value it wants to it.
You can, however, provide granular read/write access for only those specific children by using variables in your security rules:
{
"articles": {
"$key": {
"viewCounter": {
".write": true,
".validate": "newData.isNumber() && newData.val() == data.val() + 1"
},
"$other": {
".write": "auth.email == 'example#gmail.com'"
}
}
}
}
It is not possible to do any filtering based on IP addresses. You'll want to use a secret from trusted server code to do that as suggested in the comment.