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.
Related
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.
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 try to prevent the creation of already existing usernames.
This is my code where I upload usernames to the database:
ref?.child("users").child(FIRAuth.auth()!.currentUser!.uid).child("username").setValue(self.createUserName.text)
And this is the code where I try to get if the username already exists or not
ref?.child("users")
.queryOrdered(byChild: "username")
.queryEqual(toValue: self.createUserName.text?.uppercased())
.observeSingleEvent(of: .value, with: { (snapshot) in
if !snapshot.exists() {
print("😍")
}
}) { error in
print("👾")
}
The database looks like this
Database_App {
users {
-3bSRmS4PHXUwsr7XbMBwgPozNfK2 {
username: "sebas.varela"
}
}
}
And appear this line in the console:
Consider adding ".indexOn": "username" at /users to your security rules for better performance
The problem is that I always get 😍. What is the problem with this?
You can only query for values that are at a directly under the reference you query at. For your case that would be with a data model like:
Database_App {
users {
-3bSRmS4PHXUwsr7XbMBwgPozNfK2: "sebas.varela"
}
}
This will work in your code, but is hard to get secure and performant. The more common approach is to work with a extra node where you map user names to uids:
Database_App {
userNames {
"sebas,varela": "-3bSRmS4PHXUwsr7XbMBwgPozNfK2"
}
}
In this case the node essentially allows a user to claim their name. The advantage of this system is that the keys are automatically guaranteed to be unique on the server, no client-side code needed for that part.
You will want to:
add security rules that ensure a user can only claim a username that hasn't been claimed yet
also in these security rules allow a user to release their claim on a username
add client-side code to handle conflicts in a nicer way than the default "permission denied" error you'll get from the server
$id The key where this record is stored. The same as obj.$ref().key
To get the id of an item in a $firebaseArray within ng-repeat, call $id on that item.
These two are from the angular fire reference:
https://github.com/firebase/angularfire/blob/master/docs/reference.md
What I understand is if there is firebase object created with :
var object = $firebaseObject(objectRef);
then I can use uid always.
uid : object.uid
But I saw examples where the firebase auth user is used with $id.
return Auth.$requireSignIn().then(function (firebaseUser) {
return Users.getProfile(firebaseUser.uid).$loaded().then(function (profile) {
**profile.uid or profile.$id here**
Also is it possible the object to have uid but not to have $id (obj.$ref().key). Aren't they the same thing? Does the object have to be loaded first with $loaded() to use $id or uid?
Regards
You seem to be confusing two concepts:
the object.$id of an AngularFire object contains the key of that object in the Firebase Database.
a firebaseUser.uid in Firebase terms is the identification of a Firebase Authentication user.
It is common to store your Firebase Authentication users in the database under their uid, in which case user.$id would be their uid. But they are still inherently different things.
Users
uid1
displayName: "makkasi"
uid2
displayName: "Frank van Puffelen"
So if you we look at the code snippet you shared:
return Auth.$requireSignIn().then(function (firebaseUser) {
return Users.getProfile(firebaseUser.uid).$loaded().then(function (profile) {
The first line requires that the user is signed-in; only then will it execute the next line with the firebaseUser that was signed in. This is a regular JavaScript object (firebase.User), not an AngularFire $firebaseObject.
The second line then uses the firebaseUser.uid property (the identification of that user) to load the user's profile from the database into an AngularFire $firebaseObject. Once that profile is loaded, it executes the third line.
If the users are stored in the database under their uid, at this stage profile.$id and firebaseUser.uid will be the same value.