We receive Firebase warning emails about our realtime database having insecure rules. Which is true, but I am struggling with how to change them. Our application is about 2 apps with a Chat functionality between a passenger and a driver.
This is what we store in the database:
Currently we have:
{
"rules": {
".read": true,
".write": true
}
}
Access should be granted if driverId / passengerId are supplied in the database read or write request.
Help is much appreciated.
I have only basic info about Firebase rules. I hope my below rules can help or atleast give you some idea about how to approach your situation.
So basically for read and write requests, we'll check if your data has the field driverId of passengerId or not:
{
"rules": {
"chats": {
".read": data.hasChildren([‘driverId’, ‘passengerId’]) === "true"
".write": newData.hasChildren([‘driverId’, ‘passengerId’]) === "true"
}
}
}
Here, newData corresponds to the incoming data and data corresponds to the data already and curently residing in the database.
More help can be found here and here in Firebase's official documentation for security rules.
Your rules allow anyone to read & write anything anywhere. You should use authentication (to be able to identify users by their UID in the rules), structure you db so that information with different access logic is stored in different paths, then write rules to enforce your access logic.
Related
I'm a bit confused about firebase rules. This is my realtime database. Each node inside "1" is created using the firebase unique id of the user. And in the user's node there is a list of objects.
The objective is for the user to be able to create this node if it doesn't exist, and allow the user to read/write only inside this node.
I tried this but it doesn't work. I get permission error.
{
"rules": {
"1": {
"$key": {
".read": "auth != null && auth.uid == $key",
".write": "auth != null && auth.uid == $key"
}
}
}
}
Note: In the future there will be other parent nodes ("2","3" etc) So it is important to keep the "1". Also in case it matters I am using firebase anonymous sign in.
I appreciate the help.
UPDATE:
I retrieve the installationId like this:
Task<String> getIdTask = FirebaseInstallations.getInstance().getId()
and access the database like this:
FirebaseDatabase.getInstance()
.getReference()
.child("1")
.child(installationId)
Trying to access the database using above code gives this:
Listen at /1/cKYZwWrlRmSof79rtfuX82 failed: DatabaseError: Permission denied
SOLUTION:
I just realized the magnitude of my mistake. To retrieve the userId I was using
FirebaseInstallations.getInstance().getId()
instead of this which is what firebase sees as userId:
FirebaseAuth.getInstance().getCurrentUser().getUid();
Using the later one solved the issue.
Unless you'd add another node, that actually links the UID with your user ID, how shall it know about it? I'd suggest to reconsider the structure and get rid of that superfluous node altogether; just use the UID. It's not that it wouldn't be possible, to lookup values by UID ... but it might be an unfortunate database design, which ignores the given environment.
I am seeing some very unusual traffic in my cloud functions and I can only attribute them to an attack (note that I am using the free Spark plan).
The cloud function that gets spammed is:
exports.onPlayerSolvedPuzzle = functions.database.ref('/U/{userId}/CP/{puzzleId}/S')
.onCreate((snapshot, context) =>
{
...
As you can see it is designed to be called when a new data is added in my DB at the path /U/{userId}/CP/{puzzleId}/S
My DB rules specify that only an authenticated user can write data with his ID, additionally I check that this data entry doesn't exist already
{
"rules":
{
"U":
{
"$user_id":
{
".read" : false,
".write": "auth.uid != null && $user_id === auth.uid",
"CP":
{
"$puzzle_id":
{
"S":
{
".validate": "!data.exists()"
},
...
Finally, I have only enabled the "Play Games" and "Game Center" authentications so no anonymous account or email/password account can be used to modify my DB.
When the attacks happen the number of maximum simultaneous connections to my DB jumps from the average of 20 to 100, and the number of cloud function calls jumps from 1000 or less per day to 20000 or more per day eating up my monthly usage quota quite a bit.
What should I do to protect the cloud function calls that I have not already done?
P.S.: when 'onPlayerSolvedPuzzle' executes it writes to another part of the DB to increment the number of people who have solved the puzzle with this 'puzzle_id' (or create an entry if there was none). I have code in place to see if there are entries in that part of the DB that are related to 'puzzle_id' that should not exist (I know the puzzle_id of all the puzzles). When I run this code I don't see any problem at all, so I believe the function is not called with random puzzle_id in the path.
A few things to know:
There's no way to stop someone from invoking one of your public functions. All you can do is write code inside the function to determine if the request is valid and should be acted on. You might want to consider sending an user ID token with the request and verify it with the Admin SDK.
Security rules don't apply to backend code that uses the Admin SDK.
If you're observing what appears to be abusive traffic, you should reach out to Firebase support directly to report it.
I have a small, personal Firebase webapp that uses Firebase Database. I want to secure (lock down) this app to any user from a single, specific domain. I want to authenticate with Google. I'm not clear how to configure the rules to say "only users from a single, specific domain (say #foobar.com) can read and write to this database".
(Part of the issue that I see: it's hard to bootstrap a Database with enough info to make this use case work. I need to know the user's email at the time of authentication, but auth object doesn't contain email. It seems to be a chicken-egg problem, because I need to write Firebase rules that refer to data in the Database, but that data doesn't exist yet because my user can't write to the database.)
If auth had email, then I could write the rules easily.
Thanks in advance!
If you're using the new Firebase this is now possible, since the email is available in the security rules.
In the security rules you can access both the email address and whether it is verified, which makes some great use-cases possible. With these rules for example only an authenticated, verified gmail user can write their profile:
{
"rules": {
".read": "auth != null",
"gmailUsers": {
"$uid": {
".write": "auth.token.email_verified == true &&
auth.token.email.matches(/.*#gmail.com$/)"
}
}
}
}
You can enter these rules in the Firebase Database console of your project.
Here is code working fine with my database , I have set rule that only my company emails can read and write data of my firebase database .
{
"rules": {
".read": "auth.token.email.matches(/.*#yourcompany.com$/)",
".write": "auth.token.email.matches(/.*#yourcompany.com$/)"
}
}
Code which is working for me.
export class AuthenticationService {
user: Observable<firebase.User>;
constructor(public afAuth: AngularFireAuth) {
this.user = afAuth.authState;
}
login(){
var provider = new firebase.auth.GoogleAuthProvider();
provider.setCustomParameters({'hd': '<your domain>'});
this.afAuth.auth.signInWithPopup(provider)
.then(response => {
let token = response.credential.accessToken;
//Your code. Token is now available.
})
}
}
WARNING: do not trust this answer. Just here for discussion.
tldr: I don't think it's possible, without running your own server.
Here's my attempt thus far:
{
"rules": {
".read": "auth.provider === 'google' && root.child('users').child(auth.uid).child('email').val().endsWith('#foobar.com')",
".write": "auth.provider === 'google' && root.child('users').child(auth.uid).child('email').val().endsWith('#foobar.com')",
"users": {
"$user_id": {
".write": "auth.provider === 'google' && $user_id === auth.uid && newData.child('email').val().endsWith('#foobar.com')"
}
}
}
}
I believe the above says "only allow people to create a new user if they are authenticated by Google, are trying to write into the database node for themselve ($user_id === auth.uid) and their email ends in foobar.com".
However, a problem was pointed out: any web client can easily change their email (using the dev console) before the message is sent to Firebase. So we can't trust the user entry's data when stored into Firebase.
I think the only thing we can actually trust is the auth object in the rules. That auth object is populated by Firebase's backend. And, unfortunately, the auth object does not include the email address.
For the record, I am inserting my user this way:
function authDataCallback(authData) {
if (authData) {
console.log("User " + authData.uid + " is logged in with " + authData.provider + " and has displayName " + authData.google.displayName);
// save the user's profile into the database so we can list users,
// use them in Security and Firebase Rules, and show profiles
ref.child("users").child(authData.uid).set({
provider: authData.provider,
name: getName(authData),
email: authData.google.email
});
As you might be able to imagine, a determined user could overwrite the value of email here (by using the DevTools, for examples).
This should work for anyone looking for a Cloud Firestore option, inspired by Frank van Puffelen's answer.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
// Allows all users to access data if they're signed into the app with an email of the domain "company.com"
allow read, write: if request.auth.uid != null && request.auth.token.email.matches(".*#company.com$");
}
}
}
For anyone really not wanting to have unverified accounts logging in. Maybe dirty, but very effective.
This is my workaround (Angular app):
this.userService.login(this.email.value, this.password.value).then(data => {
if (data.user.emailVerified === true) {
//user is allowed
} else {
//user not allowed, log them out immediatly
this.userService.logout();
}
}).catch(error => console.log(error));
I am getting confused about Firebase Security. Here why...
For example; I have the following database:
clients: {
$key: {
//client data
}
}
trainer: {
$key: {
//trainer data
}
}
I need a client to be able to see their own information. I need a trainer to be able to see the information of all their clients, but not other trainers.
So client A, B and C can see their personal, private data. But Trainer A can only see client A and B's details (he doesn't train C).
The problem I run into is that you can't seem to request for example all clients, but only return the ones that checkout with security rules. As the docs state, if one in the list returns false, the whole bunch does.
How can I create the correct structure and security rules?
You should impliment something like adding clients to trainer as friend. Create a node sharedwith which contains trainer as child and trainer will contains A & B clients key and their data .
Similar to examples in the Firebase docs (https://firebase.google.com/docs/database/security/user-security), this might work for you.
I think at a minimum your structure could benefit from having a direct key tie between client and trainer. Something like
clients: {
clientA: {
trainerKey: "trainerA"
}
}
trainers: {
trainerA: {
clients: { clientA: true, clientB: true }
}
}
Security Rules - edited to include user
"clients": {
".read": "auth !== null && (root.child('trainers/' + $trainerKey + '/clients').child($uid).exists() || auth.uid == $uid")
}
This a) checks that a user is authenticated and b) looks to see if the client is in the list of clients for the trainer OR whether this is the client.
This is untested, but hopefully gets you where you're trying to go. I'm also making the assumption that your clients IDs are the same as their auth ID.
I've signed up for the free month trial of Azure, and I have created a Mobile Service. I'm using iOS, so I downloaded the model Todo app for iOS.
I am now trying to use Table Storage in the back end instead of a MSSQL store; I have found instructions on using Table Storage here: http://azure.microsoft.com/en-us/documentation/articles/storage-nodejs-how-to-use-table-storage/
However, my app is still storing todo items in the MSSQL storage. I've been told that I don't need to do anything in the client to make the switch, so I assume everything I need to do must be done in the node.js scripts. But I'm clearly missing something.
One thing that confuses me is that after I downloaded the generated node.js script for the Todo app, I didn't see anything in it that seemed to be explicitly talking to the MSSQL database.
Any pointers would be greatly appreciated.
EDIT:
here's my todoitem.insert.js:
var azure = require('azure-storage');
var tableSvc = azure.createTableService();
function insert(item, user, request) {
// request.execute();
console.log('Request received');
console.log(request);
var entGen = azure.TableUtilities.entityGenerator;
var task = {
PartitionKey: entGen.String('learningazure'),
RowKey: entGen.String('1'),
description: entGen.String('add something to TS'),
dueDate: entGen.DateTime(new Date(Date.UTC(2014, 11, 5))),
};
tableSvc.insertEntity('codedelphi',task, {echoContent: true}, function (error, result, response) {
if(!error){
// Entity inserted
console.log('No error on table insert: task created.');
request.respond(statusCodes.SUCCESS, 'OK.');
} else {
console.log('Houston, we have a problem. Entity not added to table.');
console.log(error);
}
});
console.log(JSON.stringify(item, null, 4));
}
tableSvc.createTableIfNotExists('codedelphi', function(error, result, response){
if(!error){
// Table exists or created
console.log('No error, table should exist');
} else {
console.log('We have a problem.');
console.log(error);
}
});
Mobile Services has the built in capability to handle talking to your SQL Database for you. When your script calls "request.execute()" that triggers whatever the request is (insert, update, delete, select) to be ran against the SQL database. Talking to Table Storage instead of SQL requires you to edit those scripts to explicitly talk to Table Storage (i.e. perform your insert, update, deletes, and reads). Today there is no magic switch which will change your "request.execute" from talking to SQL to talk to Table Storage. If you've already edited your scripts to talk to Table Storage and it's not working / you still see data stored in your SQL database, I would suspect that you are either still calling "request.execute" in your scripts, or you haven't pushed them to your Mobile Service (if you've pulled them down locally and then need to push them back to your service). If you've done all of the above, update your question with the Node.js script in question so we can see it.
As Chris pointed out, you are most likely still calling request.execute() from your table scripts. By design, this will explicitly talk to the MSSQL database you configured your application with. You will have to edit your table scripts to not perform "request.execute()" and instead interact with the TableService object.
If you follow the tutorial, and do the following:
1. Import the package.
2. Create the table service object.
3. Create an entity (and modify the variables to store the data you need)
4. Write the entity to your table service.
You should see data being written to table storage rather than SQL database.
Give it a shot and ping back, we'll help you out.