Firebase rules pass in simulator but fail in practice - AngularFire2

392 views
Skip to first unread message

Todd Rothe

unread,
May 9, 2018, 5:31:42 PM5/9/18
to Firebase Google Group
Firebase data structure
{root}
dev : 
  users : 
    key : userObj
      uid : key,
      uuidc : auth.uid of this user
    key : userObj
    key : userObj...

  students : 
    key : studentObj
       id : key,
       uid: key of user/creator,
       uuidc: auth.uid of user/creator (not student),
    key : studentObj
    key : studentObj...


Firebase rules : accessing student records pass when using the simulator in the firebase console
{
  "rules": {
    "dev": {
      "users": {
        ".indexOn": [
          "uid",
          "uuidc"
        ]
      },
      "students": {
        "$id": {
          ".read": "data.child($id).child('uuidc').val() == auth.uid",
          ".write": "data.child($id).child('uuidc').val() == auth.uid",
        },
        ".indexOn": [
          "id",
          "uid",
          "uuidc"
        ]
      },


TS 
this.afDb.database.ref('dev/students'))
          .orderByChild('uid').equalTo(this.user.uid)
          .once('value', (snap) => { ... })



I've also tried query rules 
".read": "auth.uid != null && query.orderByChild == 'uuidc' && query.equalTo == auth.uid",

And updating my query to 
this.afDb.database.ref('dev/students'))
          .orderByChild('uuidc').equalTo(auth.uid)
          .once('value', (snap) => { ... })


I create a new user like this
  let ref = this.afDb.database.ref('dev/users/').push(newUserObj);
  ref.update({ uid: ref.key });

I create a new student like this
  let ref = this.afDb.database.ref('dev/students/').push(newStudentObj);
  ref.update({ id: ref.key, uid: user.uid});

And all my queries are based on the uid.

As part of researching security rules i decided to write a script that would add a 'uuidc' prop to all objects. This prop holds the auth.uid of the object creator.

When i add the above security rules and run my app, attempting to load all student records associated with a given user fails with the following error
core.js:1448 ERROR Error: permission_denied at /dev/students: Client doesn't have permission to access the desired data.
    at Object.exports.errorForServerCode (util.js:513)
    at onComplete (SyncTree.js:538)
    at Object.eval [as onComplete] (Repo.js:115)
    at eval (PersistentConnection.js:189)
    at PersistentConnection.onDataMessage_ (PersistentConnection.js:444)
    at Connection.onDataMessage_ (Connection.js:262)
    at Connection.onPrimaryMessageReceived_ (Connection.js:256)
    at WebSocketConnection.eval [as onMessage] (Connection.js:157)
    at WebSocketConnection.appendFrame_ (WebSocketConnection.js:197)
    at WebSocketConnection.handleIncomingFrame (WebSocketConnection.js:247)

Any help is greatly appreciated!

     node 6.11.0

    "@angular/core": "5.2.9",
    "angularfire2": "^5.0.0-rc.5-next",
    "firebase": "^4.8.0",

Kato Richardson

unread,
May 11, 2018, 8:07:26 AM5/11/18
to Firebase Google Group
Hi Todd!

Looks like you your security rule is trying to look at the path /dev/users/$uid/$uid/uuidc. You might just need to remove the .child($id) to make that work.

☼, Kato

--
You received this message because you are subscribed to the Google Groups "Firebase Google Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to firebase-tal...@googlegroups.com.
To post to this group, send email to fireba...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/firebase-talk/c2a3cc19-aaf1-4fdc-bfea-9b7a6a0338f2%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


--

Kato Richardson | Developer Programs Eng | kato...@google.com | 775-235-8398

Todd Rothe

unread,
May 11, 2018, 2:04:03 PM5/11/18
to Firebase Google Group
Hi Kato,
Huge thanks for the reply! That makes a lot of sense so i tried this

"students": { 
        "$id": {
        ".read": "root.child('dev/students').child($id).child('uuidc').val() == auth.uid",
        ".write": "auth.uid != null",          
        }, 

and 

"students": { 
        ".read": "data.child('uuidc').val() == auth.uid",

Both pass as expected in the simulator when my auth token payload contains a uid that matches the student.uuidc.
Both fail as expected in the simulator when my auth token payload contains a uid that doesn't match the student.uuidc.

Both fail unexpectedly when queried by my app -
this.afDb.database.ref(this.utils.env.concat('students'))
          .orderByChild('uuidc').equalTo({auth.user.uid})
          .once('value', (snap) => {

Thanks for getting me one step closer, but still running into this issue.

Kato Richardson

unread,
May 14, 2018, 3:15:06 PM5/14/18
to Firebase Google Group
Hi Todd,

For Realtime Database, a query will only pass if the entire path is queryable. Thus, you can't query /dev/users unless it's possible to read all data in that node, including children. See Rules are not filters.

We should probably backtrack a bit and talk about the use case we're trying to solve here before I spend much time trying to type up possible solutions (see XY problem). Is the goal to store students relative to a "class" and grant instructors access to the classes? Is the relationship between students and instructors 1:1? Are students viewed or accessed any other way than simply by the instructor's uid?

☼, Kato


For more options, visit https://groups.google.com/d/optout.

Todd Rothe

unread,
May 15, 2018, 11:56:26 PM5/15/18
to Firebase Google Group
Thanks for your time and insight Kato!

A Teacher has many Students. A Student has one Teacher.
Access to a Student record should be restricted to only that one Teacher user (via uuidc) and that one Student user / self (via uuids).
There are other collections; messages, tasks etc. that are stored separate from the Student record and have uuidc/uuids props which I use to query records specific to the Teacher/Student respectively. I'm expecting to apply the same rules to those dev/messages, dev/tasks, etc.

Thanks,
Todd

Todd Rothe

unread,
May 18, 2018, 3:07:41 PM5/18/18
to Firebase Google Group
Hoping someone has some insight on what i'm doing wrong.

Kato Richardson

unread,
May 18, 2018, 3:22:24 PM5/18/18
to Firebase Google Group
Hey Todd,

Sorry for the delay. I got some advice from Puf a few days ago but hadn't circled back to this. Thanks for pinging the thread.

It seems like our query-based rules might be able to pull this off. We haven't tested any of this, but it seems like something along these lines:

```
"dev": {
"students": {
".read": "auth.uid != null &&
            query.orderByChild == 'uid' &&
            query.equalTo == auth.uid
",
}
}
```

Hopefully that gets you a decent starting point. If you have trouble with it, I can fiddle more and see if I can work out the kinks.

☼, Kato


For more options, visit https://groups.google.com/d/optout.

Todd Rothe

unread,
May 18, 2018, 8:26:46 PM5/18/18
to Firebase Google Group
That is a good start for sure!
What about the scenario where i query based on student? Teacher selects one of their many students and then loads the 'tasks' view to see that students assignments. 

Query is something like this
database.ref(('dev/tasks')).orderByChild('sid').equalTo(this.teacher.selectedStudent.sid);

In an effort to keep the data flat i've been storing all/most object types in their own collections (students, tasks, messages, payments, etc) and adding uuidt(eacher = user auth.uid), uuids(tudent = student user auth.uid), sid (student unique key) and so on. Fingers crossed this was not a misguided attempt to follow the best practices laid out in the docs?

,Todd

Kato Richardson

unread,
May 19, 2018, 12:00:07 AM5/19/18
to Firebase Google Group
I think that should be fine. I think the primary issue with accessing the students/ path was that you had an extra `data.child($id)` in there.

Hm, not really sure I can reconcile that with the sample data you showed in your original post. I think you mean that the instructor is querying using sid and that's also the record key? So something like /dev/students/<student sid here>? And uuidc was the teacher's id? 

Assuming I've gotten those details right, then teachers could directly read a student record by adding an additional rule at the child level:

```
"dev": {
"students": {
   // grants query access at /dev/students

   ".read": "auth.uid != null &&
            query.orderByChild == 'uid' &&
            query.equalTo == auth.uid
",
   "$student_id": {
        // grants read access at a specific student record to instructor or student
        ".read": "auth.uid == $student_id || auth.uid == data.child('uuidc').val()"
    }
}
}
```

Hope that's what you're looking for. : )

☼, Kato



For more options, visit https://groups.google.com/d/optout.

Todd Rothe

unread,
May 21, 2018, 11:26:05 PM5/21/18
to Firebase Google Group
I think you've hit on the core of my issue. I'm not sure why this rule passes in the simulator but blocks access from within my app

    auth.uid == data.child('uuidc').val()

When viewed in or printed to the console, the values appear to be the same for auth.uuid and collection members (children of /dev/students or /dev/tasks).
If I invert the rule then access is granted so it seems that the values must be different in some way?

    auth.uid != data.child('uuidc').val()


In my app this prints out - true -

    console.log(this.authUser.uid === item.uuidc);


,Todd

Todd Rothe

unread,
May 22, 2018, 10:14:04 AM5/22/18
to Firebase Google Group
Oh, also the query rule does not cause a failure and the query return the expected list, ie. appears to eval the item.uuidc == auth.uid

".read": "auth.uid != null && 
    query.orderByChild == 'uuidc' && 
    query.equalTo == auth.uid"

Query:
this.afDb.list('/dev/students', ref => ref.orderByChild('uuidc').equalTo(this.user.uuid)).valueChanges()...


,Todd

Todd Rothe

unread,
May 22, 2018, 10:14:04 AM5/22/18
to Firebase Google Group
So an update - it fails in the simulator too. 
I wish the simulator showed you the values of all the elements involved in the fail. 
Opening the failure details at the top of the simulator show the Auth.uid and .provider and the path I'm attempting to access, but it doesn't show me the data.child('uuidc').val()

I'm feeling very close to feeling very dumb... in that good way.

,Todd

Kato Richardson

unread,
May 22, 2018, 10:34:30 AM5/22/18
to Firebase Google Group
Okay, so other than the lack of feedback and detail in the simulator (agreed and a known issue, I'll bring it up again), we're good now?

Clutching my celebratory fanfare in anticipation...


For more options, visit https://groups.google.com/d/optout.

Todd Rothe

unread,
May 22, 2018, 3:31:04 PM5/22/18
to Firebase Google Group
Partly. Using the updated query will safely get me any record type that I can query based on uuidc / auth.uid.
This will get me core teacher data and a list of students.


The outstanding scenario effects most other data, tasks, for example. If I were to query tasks by uuidc then I would get back tasks for all students of a teacher.
Currently I query tasks by sid where sid = teacher.selectedStudent.uniqueId. Looks like the following would solve for that but this is what is causing read fails.

auth.uid == data.child('uuidc').val()



PS. FWIW i was getting false success when this was in place
  "dev": {
      ".read": "auth.uid != null",
      "users": {...},
      "students": {...},
      "tasks": {...}

Todd Rothe

unread,
May 24, 2018, 2:38:16 PM5/24/18
to Firebase Google Group
Bump

Todd Rothe

unread,
May 31, 2018, 7:56:53 PM5/31/18
to Firebase Google Group
Hi Kato,
I've created a simplified project with just the libs required to reproduce the issue.
Using a new firebase rtdb with just the required data I am able to reproduce the apparent failure when i put this rule in place.

"students": {
        ".read": "auth.uid != null && auth.uid == data.child('uuidc').val()",
}

Is there a way we can take this offline and i can send u a zip of this project?

Kato Richardson

unread,
Jun 1, 2018, 11:57:13 AM6/1/18
to Firebase Google Group
Hi Todd,

Sorry, been heads down in some projects last couple of weeks, as I'm sure you've noticed indirectly.

Could you set up a stackblitz that demonstrates this and include the complete security rules? I'd rather not have access to your project or a zip, but if you can make it run in stackblitz I'll have a look.

☼, Kato


For more options, visit https://groups.google.com/d/optout.

Todd Rothe

unread,
Jun 4, 2018, 5:47:44 PM6/4/18
to Firebase Google Group
Hey Kato,

The environment.ts is stripped so none of my api keys are public.
Please let me know what else I can provide.

Thanks!

Todd Rothe

unread,
Jun 6, 2018, 1:36:48 PM6/6/18
to Firebase Google Group
Bumping this guy.

Kato Richardson

unread,
Jun 14, 2018, 9:24:51 PM6/14/18
to Firebase Google Group
Hi Todd,

Sorry for the long delay in replying. I wasn't able to get your stackblitz running; it's a bit too complex for me to grok--looks like a full app rather than a minimal repro. So it took me a while to try and recreate this and try it out.

I was able to take the security rules we discussed and some semblance of your data and verify that I can run it in the simulator and perform queries on it with the same results. So it looks like some issue in the code or rules setup.

Here's my working version, which will hopefully get you unblocked:


One interesting thing I ran across is that the rules seemed to fail if I did not have a .indexOn rule set up (shows a warning in the client console to this affect). In retrospect, that makes some sense if I think it through, since I can't download all data from the server, which is required if no index exists. Something to keep in mind.

☼, Kato


For more options, visit https://groups.google.com/d/optout.

Todd Rothe

unread,
Jun 15, 2018, 9:05:27 PM6/15/18
to Firebase Google Group
Thank you so much Kato! So far that all makes sense and I can see how it's working.
Is it true that data.child().val() is only accessible when targeting a single record? This would be my learning from your StackBlitz.

I was trying to use data.child().val() as if 
data = query result (before returned to the client) 
.child = each node within that result
('uuidc') = uuidc property of the child

Here is an extended data set that includes tasks. 
I've forked the editable StackBlitz you created and added a button and the query function to fetch tasks based on studentid - https://stackblitz.com/edit/angular-9mrwst
My question is, is there a way I can query tasks based on studentid and evaluate read access based on uuidc?

{
  "students": {
    "CdLeh473zsPMX3I1jY0sOQOla032": {
      "name": "Student F",
      "uuidc": "lAkWa9BqDDZuOGn6xAGa8FZ6giA3"
    },
    "student1": {
      "name": "Student A",
      "uuidc": "IZW1vvkpL2UOt9lcdhenWeBBboh2"
    },
    "student2": {
      "name": "Student B",
      "uuidc": "IZW1vvkpL2UOt9lcdhenWeBBboh2"
    },
    "student3": {
      "name": "Student C",
      "uuidc": "IZW1vvkpL2UOt9lcdhenWeBBboh2"
    },
    "student4": {
      "name": "Student D",
      "uuidc": "lAkWa9BqDDZuOGn6xAGa8FZ6giA3"
    },
    "student5": {
      "name": "Student E",
      "uuidc": "lAkWa9BqDDZuOGn6xAGa8FZ6giA3"
    }
  },
  "tasks": {
  "task1": {
  "name": "Student A Task 1",
      "uuidc": "IZW1vvkpL2UOt9lcdhenWeBBboh2"
      "sid": "student1"
  },
  "task2": {
  "name": "Student B Task 1",
      "uuidc": "lAkWa9BqDDZuOGn6xAGa8FZ6giA3"
      "sid": "student2"
  },
  "task3": {
  "name": "Student A Task 2",
      "uuidc": "IZW1vvkpL2UOt9lcdhenWeBBboh2"
      "sid": "student1"
  },
  "task4": {
  "name": "Student A Task 3",
      "uuidc": "IZW1vvkpL2UOt9lcdhenWeBBboh2"
      "sid": "student1"
  },
  "task5": {
  "name": "Student C Task 1",
      "uuidc": "lAkWa9BqDDZuOGn6xAGa8FZ6giA3"
      "sid": "student3"
  },
  "task6": {
  "name": "Student B Task 2",
      "uuidc": "lAkWa9BqDDZuOGn6xAGa8FZ6giA3"
      "sid": "student2"
  }
  }
}

Huge thanks for all your time on this!
Reply all
Reply to author
Forward
0 new messages