Restrict Firebase users by email - firebase

I have a client that would like to be able to make a list of restricted emails that can access the data. So anyone else coming to the app can't read/write any data at all ( ideally can't even log in but I don't think that's possible with Firebase? ). Any ideas on how to go about this? I had thought of having an array of accepted emails and checking whether their email existed in the security rules but that didn't seem to work. I had the following in the database:
"validEmails": ["test#test.com"]
and then in the security rules:
".read": "root.child('validEmails').val().indexOf(auth.token.email) > -1"
But it looks like you can't use indexOf in those security rules.
Maybe I need to have a list of acceptable emails, and then when a user signs up it checks whether they're in that list and adds their UID to an accepted list? I guess I could do this through a cloud function or something?
Any help would be much appreciated.
Cheers

Have the list of allowed user's emails in the database:
"whitelist": {
"fred#gmail%2Ecom": true,
"barney#aol%2Ecom": true
}
Since periods are not allowed in keys, you need to escape strings with periods before storing them.
Then in the database rules:
{
"rules": {
"whitelist": {
".read": false,
".write": false
},
".read": "root.child('whitelist').child(auth.token.email.replace('.', '%2E')).exists()",
".write": "root.child('whitelist').child(auth.token.email.replace('.', '%2E')).exists()"
}
}
User's email is accessible via auth.token.email. You need to escape the dots (. -> %2E) and check if the key exists on the whitelist.
These rules don't allow anyone read or write access to the /whitelist section of the database. Modification is only possible via firebase console.

Thanks guys, what I ended up doing was having a list of acceptable emails:
{
"validEmails": ["test#test.com"],
"validUsers": {}
}
and then have a cloud function run to check when a user signed up if their email was in the valid email list. If it was then it added them to the valid users list and if not it deleted the newly created user. I also set up data rules so that only users within validUsers could access the data.
The front-end then handled the redirection etc for invalid users.

Once you enable the authentication module of Firebase I believe you can't restrict it to email addresses or domains. However you could secure your database another way. If your users are already registered and you know their uid, then you can restrict read and write access based on these.
Lets pretend you have an acl object in the database, you can list the users and their uid with their read/write permissions.
These rules will check each request and only allow authorised users to access the data.
{
"acl": {
[
{
"uid: "abc123"
"canRead": true,
"canWrite": true
},
{
"uid": "def456",
"canRead": true,
"canWrite": false
}
},
"secure": {
".read": { root.child('acl').child(auth.uid).child('canRead').val() == true }
".write": { root.child('acl').child(auth.uid).child('canWrite').val() == true }
}
}

Related

Firebase rules warning - any user can read your entire database [duplicate]

I recently received an email from firebase telling me that my realtime database has insecure rules. These are the rules that I have set:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
Is this not a secure rule?
Email/Password is the only sign-in method that I have enabled.
firebaser here
I'm sorry if the email wasn't very explicit about what isn't secure about those rules. Securing your user's data is a crucial step for any app that you make available, so I'll try to explain a bit more about how that works below.
The (default) rules you have allow anyone who is signed in to your back-end full read/write access to the entire database. This is only a very basic layer of security.
On the one hand this is more secure than just granting everyone access to your database, at least they have to be signed in.
On the other hand, if you enable any auth provider in Firebase Authentication, anyone can sign in to your back-end, even without using your app. Depending on the provider, this can be as easy as running a bit of JavaScript in your browser's developer console. And once they are signed in, they can read and write anything in your database. This means they can delete all data with a simple command like firebase.database().ref().delete().
To make the data access more secure, you'll want to more tightly control what each signed-in user can do. For example, say that you keep a profile with information about each user under /users. You might want to allow all users to access these profiles, but you definitely want users to only be allowed to modify their own data. You can secure this with these rules:
{
"rules": {
"users": {
".read": true,
"$user_id": {
// grants write access to the owner of this user account
// whose uid must exactly match the key ($user_id)
".write": "$user_id === auth.uid"
}
}
}
}
With these rules, everyone (even non-authenticated users) can read all profiles. But each profile can only be modified by the user whose profile it is. For more on this, see the Firebase documentation on securing user data.
In addition to ensuring that all access to data is authorized, you'll also want to ensure that all data stored is valid to whatever rules you have for you app. For example, say that you want to store two properties for a user: their name, and their age (just for the sake of the example, in reality you'd probably store their date-of-birth instead). So you could store this as something like:
"users": {
"uidOfPuf": {
"name": "Frank van Puffelen",
"age": 48
}
}
To ensure only this data can be written, you can use this rules:
{
"rules": {
"users": {
".read": true,
"$user_id": {
".write": "$user_id === auth.uid",
".validate": "data.hasChildren('name', 'age')",
"name": {
".validate": "data.isString()",
},
"age: {
".validate": "data.isNumber()",
},
"$other: {
".validate": false
}
}
}
}
}
These rules ensure that each user profile has a name and age property with a string and numeric value respectively. If someone tries to write any additional properties, the write is rejected.
Above is a quick primer on how to think about securing your (user's) data. I recommend that you check out the Firebase security documentation (and the embedded video) for more.
Update: since May 2021 you can also use Firebase App Check to restrict access to calls just coming from your web site or app. This is another, quick way to reduce the abuse of your database. This approach is not foolproof though, so you'll want to combine App Check for broad protected, with the security rules for fine-grained control.
You can also mute alerts by visiting the link at the bottom of the email.
https://console.firebase.google.com/subscriptions/project/<YOUR_PROJECT_NAME>
I changed rules
{
"rules": {
"users": {
".read": true,
"$user_id": {
// grants write access to the owner of this user account
// whose uid must exactly match the key ($user_id)
".write": "$user_id === auth.uid"
}
}
}
}
But after that page not working.

Firebase Database Rules - rules based on custom claims

I have a firebase realtime database. Currently it contains two nodes admin and common
{
"admin" : {
"adminval" : 9898574632,
"adminval1" : 645354536,
"adminval2" : 7776756433
},
"common" : {
"commonval" : 123433221
}
}
I added to each user custom claim roles which describes roles user has in my system. It looks like this
{'roles': ['ROLE_ADMIN', 'ROLE_USER']}
Now I would like to restrict access so only users with claim ROLE_ADMIN are allowed to read/write admin node and with either of the roles can read/write node common.
How to do it ? I tried something like it:
{
"rules": {
"admin": {
".read": "auth.token.roles.contains('ROLE_ADMIN')",
".write": "auth.token.roles.contains('ROLE_ADMIN')"
}
"common": {
".read": "auth.token.roles.contains('ROLE_USER') || auth.token.rules.contains('ROLE_ADMIN')",
".write": "auth.token.roles.contains('ROLE_USER') || auth.token.rules.contains('ROLE_ADMIN')"
}
}
}
I am afraid we cannot use Array or List in Realtime database security rules. Language of rules can work with only limited set of types. You can check the documentation here https://firebase.google.com/docs/reference/security/database.
We can walk around this limitation for instance by using String in claims:
{'roles': 'ROLE_ADMIN,ROLE_USER'}
Now we can check the appropriate role using
".read": "auth.token.roles.contains('ROLE_USER') || auth.token.rules.contains('ROLE_ADMIN')"
, because String supports contains method. Another option, as you suggest in your comment, is to set claims like
{'role_admin': true, 'role_user': true}
and then check the role in rules
".read": "auth.token.role_admin == true || auth.token.role_user == true"
Side note: we can use List and in operator in Firestore security rules, see doc here https://firebase.google.com/docs/reference/rules/rules.List.
I've never used arrays in custom claims, so am not sure how that works, or why it doesn't work for you.
But I did notice that you're doing a weird mix of role-based access control and level-based access control in your rules, so want to give some general hints/observations:
You're marking your admins with both roles: ROLE_ADMIN and ROLE_USER.
But then you have an OR in the common node, checking for either role. This means there's no need to give the admin the ROLE_USER too.
Keep in mind that custom claims are limited to 1000 bytes and are sent with every request. I'd highly recommend dropping the ROLE_ prefix, since that's already implied in the roles name of the claim.
If you have a universal logic that admins can see more than regular users, consider using a numeric access_level claim instead. Say a regular user is level: 1, while an admin is level: 2. You can then in your claims check for the minimum level: ".read": "auth.token.access_level >= 1"

Firebase Auth with Firestore [duplicate]

I recently received an email from firebase telling me that my realtime database has insecure rules. These are the rules that I have set:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
Is this not a secure rule?
Email/Password is the only sign-in method that I have enabled.
firebaser here
I'm sorry if the email wasn't very explicit about what isn't secure about those rules. Securing your user's data is a crucial step for any app that you make available, so I'll try to explain a bit more about how that works below.
The (default) rules you have allow anyone who is signed in to your back-end full read/write access to the entire database. This is only a very basic layer of security.
On the one hand this is more secure than just granting everyone access to your database, at least they have to be signed in.
On the other hand, if you enable any auth provider in Firebase Authentication, anyone can sign in to your back-end, even without using your app. Depending on the provider, this can be as easy as running a bit of JavaScript in your browser's developer console. And once they are signed in, they can read and write anything in your database. This means they can delete all data with a simple command like firebase.database().ref().delete().
To make the data access more secure, you'll want to more tightly control what each signed-in user can do. For example, say that you keep a profile with information about each user under /users. You might want to allow all users to access these profiles, but you definitely want users to only be allowed to modify their own data. You can secure this with these rules:
{
"rules": {
"users": {
".read": true,
"$user_id": {
// grants write access to the owner of this user account
// whose uid must exactly match the key ($user_id)
".write": "$user_id === auth.uid"
}
}
}
}
With these rules, everyone (even non-authenticated users) can read all profiles. But each profile can only be modified by the user whose profile it is. For more on this, see the Firebase documentation on securing user data.
In addition to ensuring that all access to data is authorized, you'll also want to ensure that all data stored is valid to whatever rules you have for you app. For example, say that you want to store two properties for a user: their name, and their age (just for the sake of the example, in reality you'd probably store their date-of-birth instead). So you could store this as something like:
"users": {
"uidOfPuf": {
"name": "Frank van Puffelen",
"age": 48
}
}
To ensure only this data can be written, you can use this rules:
{
"rules": {
"users": {
".read": true,
"$user_id": {
".write": "$user_id === auth.uid",
".validate": "data.hasChildren('name', 'age')",
"name": {
".validate": "data.isString()",
},
"age: {
".validate": "data.isNumber()",
},
"$other: {
".validate": false
}
}
}
}
}
These rules ensure that each user profile has a name and age property with a string and numeric value respectively. If someone tries to write any additional properties, the write is rejected.
Above is a quick primer on how to think about securing your (user's) data. I recommend that you check out the Firebase security documentation (and the embedded video) for more.
Update: since May 2021 you can also use Firebase App Check to restrict access to calls just coming from your web site or app. This is another, quick way to reduce the abuse of your database. This approach is not foolproof though, so you'll want to combine App Check for broad protected, with the security rules for fine-grained control.
You can also mute alerts by visiting the link at the bottom of the email.
https://console.firebase.google.com/subscriptions/project/<YOUR_PROJECT_NAME>
I changed rules
{
"rules": {
"users": {
".read": true,
"$user_id": {
// grants write access to the owner of this user account
// whose uid must exactly match the key ($user_id)
".write": "$user_id === auth.uid"
}
}
}
}
But after that page not working.

Firebase email saying my realtime database has insecure rules

I recently received an email from firebase telling me that my realtime database has insecure rules. These are the rules that I have set:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
Is this not a secure rule?
Email/Password is the only sign-in method that I have enabled.
firebaser here
I'm sorry if the email wasn't very explicit about what isn't secure about those rules. Securing your user's data is a crucial step for any app that you make available, so I'll try to explain a bit more about how that works below.
The (default) rules you have allow anyone who is signed in to your back-end full read/write access to the entire database. This is only a very basic layer of security.
On the one hand this is more secure than just granting everyone access to your database, at least they have to be signed in.
On the other hand, if you enable any auth provider in Firebase Authentication, anyone can sign in to your back-end, even without using your app. Depending on the provider, this can be as easy as running a bit of JavaScript in your browser's developer console. And once they are signed in, they can read and write anything in your database. This means they can delete all data with a simple command like firebase.database().ref().delete().
To make the data access more secure, you'll want to more tightly control what each signed-in user can do. For example, say that you keep a profile with information about each user under /users. You might want to allow all users to access these profiles, but you definitely want users to only be allowed to modify their own data. You can secure this with these rules:
{
"rules": {
"users": {
".read": true,
"$user_id": {
// grants write access to the owner of this user account
// whose uid must exactly match the key ($user_id)
".write": "$user_id === auth.uid"
}
}
}
}
With these rules, everyone (even non-authenticated users) can read all profiles. But each profile can only be modified by the user whose profile it is. For more on this, see the Firebase documentation on securing user data.
In addition to ensuring that all access to data is authorized, you'll also want to ensure that all data stored is valid to whatever rules you have for you app. For example, say that you want to store two properties for a user: their name, and their age (just for the sake of the example, in reality you'd probably store their date-of-birth instead). So you could store this as something like:
"users": {
"uidOfPuf": {
"name": "Frank van Puffelen",
"age": 48
}
}
To ensure only this data can be written, you can use this rules:
{
"rules": {
"users": {
".read": true,
"$user_id": {
".write": "$user_id === auth.uid",
".validate": "data.hasChildren('name', 'age')",
"name": {
".validate": "data.isString()",
},
"age: {
".validate": "data.isNumber()",
},
"$other: {
".validate": false
}
}
}
}
}
These rules ensure that each user profile has a name and age property with a string and numeric value respectively. If someone tries to write any additional properties, the write is rejected.
Above is a quick primer on how to think about securing your (user's) data. I recommend that you check out the Firebase security documentation (and the embedded video) for more.
Update: since May 2021 you can also use Firebase App Check to restrict access to calls just coming from your web site or app. This is another, quick way to reduce the abuse of your database. This approach is not foolproof though, so you'll want to combine App Check for broad protected, with the security rules for fine-grained control.
You can also mute alerts by visiting the link at the bottom of the email.
https://console.firebase.google.com/subscriptions/project/<YOUR_PROJECT_NAME>
I changed rules
{
"rules": {
"users": {
".read": true,
"$user_id": {
// grants write access to the owner of this user account
// whose uid must exactly match the key ($user_id)
".write": "$user_id === auth.uid"
}
}
}
}
But after that page not working.

Basic user authentication with records in AngularFire

Having spent literally days trying the different, various recommended ways to do this, I've landed on what I think is the most simple and promising. Also thanks to the kind gents from this SO question: Get the index ID of an item in Firebase AngularFire
Curent setup
Users can log in with email and social networks, so when they create a record, it saves the userId as a sort of foreign key.
Good so far. But I want to create a rule so twitter2934392 cannot read facebook63203497's records.
Off to the security panel
Match the IDs on the backend
Unfortunately, the docs are inconsistent with the method from is firebase user id unique per provider (facebook, twitter, password) which suggest appending the social network to the ID. The docs expect you to create a different rule for each of the login method's ids. Why anyone using >1 login method would want to do that is beyond me.
(From: https://www.firebase.com/docs/security/rule-expressions/auth.html)
So I'll try to match the concatenated auth.provider with auth.id to the record in userId for the respective registry item.
According to the API, this should be as easy as
In my case using $registry instead of $user of course.
{
"rules": {
".read": true,
".write": true,
"registry": {
"$registry": {
".read": "$registry == auth.id"
}
}
}
}
But that won't work, because (see the first image above), AngularFire sets each record under an index value. In the image above, it's 0. Here's where things get complicated.
Also, I can't test anything in the simulator, as I cannot edit {some: 'json'} To even authenticate. The input box rejects any input.
My best guess is the following.
{
"rules": {
".write": true,
"registry": {
"$registry": {
".read": "data.child('userId').val() == (auth.provider + auth.id)"
}
}
}
}
Which both throws authentication errors and simultaneously grants full read access to all users. I'm losing my mind. What am I supposed to do here?
I don't think you want to store user-specific data under a non-user-specific index. Instead of push()ing to your firebase reference, store the user data behind a meaningful key.
e.g.
auth.createUser(email, password, function(error, user) {
if (!error) {
usersRef.child(user.id).set(stuff);
}
});
Now you can actually fetch user data based on who is authenticated.
The custom Auth in the forge's simulator isn't the greatest but if you hit the tab key after selecting the input, it lets you paste or edit the field. At which point you can add {"provider":"facebook","id":"63203497"} or {"provider":"twitter","id":"2934392"} and hopefully get some useful debug out of it.
Assuming your firebase is something like:
{"registry":{
"0":{
"id":"abbacadaba123",
"index":"0",
"name":"New Device",
"userId":"facebook63203497"},
"1":{
"id":"adaba123",
"index":"1",
"name":"Other Device",
"userId":"twitter2934392"}
}
}
This may work for security rules:
{
"rules": {
"registry":{
"$registryId":{
".read":"data.child('userId').val() === (auth.provider + auth.id)",
".write":"(data.child('userId').val() === (auth.provider + auth.id))||(auth != null && !data.exists())",
".validate": "newData.hasChildren(['id', 'index', 'name', 'userId'])",
"id": {
".validate":"newData.isString()"
},
"index": {
".validate":"newData.isNumber()"
},
"name": {
".validate":"newData.isString() && newData.val().length >= 1"
},
"userId": {
".validate":"newData.val() === (auth.provider + auth.id)"
}
}
}
}
}
Your read rule tested as expected. The facebook user read-tests true on registry 0 and false on 1. The twitter user is false on 0 and true on 1.
I did a couple quick tests on the .write and .validate rules and they seem to work.
Hope this helps at least rule out the firebase security rules portion of things, so you can focus on the AngularFire binding part.

Resources