Transcript from the "Security Rules" Lesson
>> All right, so at this point in our app, we've done all these great things but nothing is secure, and that's quite a long time to get to, we still having nothing secure. But it's okay, cuz we're gonna fix that now. So with security rules, they determine access to a Firebase resource like firestore, and it says, hey, can this operation succeed?
[00:00:19] And what is really great about security rules is, is you've ever written security code, or check it, any type of server checking code, you can find that it can happen everywhere. You can say if I'm running an MVC system, when the post request comes in, I'm gonna check all this information about them.
[00:00:35] And then when I send it off to the database, I might check some more information about them. And so there might be multiple levels of security checks going on. And keeping those in the same state can be difficult and can lead to bugs. With Firebase, there is one place to write security logic, and that is Security Rules.
[00:00:53] So you only need to write your security logic right there, and from there on out, you don't have to worry about any other spots. You still have to do client side validation, that is more of a UI, that is more of a user feedback thing. A client-side validation is not security.
[00:01:24] A lot of people like Dart, or Go, or Rust, people like those quite a bit. Well, if you think about how security rules work, they have to run each and every time a request goes into your database, and that operation cannot be allowed or rejected until it computes the results of that.
[00:01:42] So what we did was, we had a whole team of people who wrote something to be as fast as possible executing, and that involves creating our own language. So while it is a sort of like a new DSL to learn, it is operate just lightning fast so it doesn't lock up your database for all these crazy extended points of time, they act very quickly.
[00:02:04] So the whole premise behind security rules is, Matching. So if you've ever written router code before with Express JS in this case, I can get the user of david_123. So if I make a get request to that, I can get that response that matches at that path. Security rules works the same way.
[00:02:24] I say match @ /users of the document, david_123. That matches right there. And now from here, I can do some sort of action based upon allowing a read or a write at this location. And what's important to understand is that, all matches that occur happen at the document level, we don't match at the collection level.
[00:02:42] And you might think, well, how can I secure a whole collection if I have to match out a document? And you can do that through the use of Wildcards. So just like routers that you've used before, you've probably used :id or whatever they use, sometimes referred to as a slug.
[00:02:59] And from there, I can get whatever variable would be held in that id. So in the case of /users:id, it could be /users david_123 or something like that. And then I can use that id to go off into the back end and get their value. I can do the same thing in security rules.
[00:03:17] I can say, hey, match users at this uid. And then from there, I can even use that uid value to check some important information to see whether that operation should be allowed. So, that is a lot of getting into that. So I am going to just start writing some rules, cuz I think rules are great.
[00:03:37] Look at that, I've rules right up here. I'm gonna do, actually no, I don't want it in this database though, I want it in this one, cuz the other one has a ton of data in it. All right, great, let's go to rules. Someway for me it is very zoomed in, all right, so the way rules work is that, we always specify rules version.
[00:03:58] This is on rules version 2. There's rule version 1 as well, and then there's only 1 and 2. They have different rules. So you always specify that you wanna be on what version. I recommend being on 2 because it's sort of like our latest and greatest, it doesn't change very often.
[00:04:11] Often it's happened twice in five, six years. And then we specify what service this needs to be on. So we're saying, Service Cloud FireStore, because we can also have other services there. We have Firebase Storage which uses the same types of rules, so there's different services in which we can match on.
[00:04:29] Then we always have this, this is basically a wildcard that matches all of the documents that live within your database. And then from here, we can start matching at paths that we care about in our application. So this right here, this =**, this is referred to as the recursive wildcard.
[00:04:49] I could change this to anything I wanted to, it's just a variable. But what this recursive wildcard does and you will probably not write this too much, but it's important to kind of just get familiar with it. Is it says, I'm going to apply these rules to every single path below me.
[00:05:10] And in this case, this is every spot in the database. So this is saying, every single spot in the database will allow a read or write if the time that the request came in is less than June 18th. I don't wanna do that because that's super permissive. So let's start by saying, okay, let's match for users.
[00:05:35] So let's match for user id, and let's say I want to say only users can read and write to their own profiles. I don't want anybody coming out and reading their profiles or being able to edit their profile. Well, the heart of security rules is, allow. Allow is a statement that says, I'm going to allow some sort of permission if some expression evaluates to true.
[00:06:01] And that is all security rules are, matched at a path and state, what permission can be allowed based on some expression? So a permission tends to be a crud style operation. We have read, we have write, and we even have more granular. For read you can say, get, only allowing get on this document, but you can also say allow listing on the collection level.
[00:06:25] So I'm not gonna allow people to just be able to list whatever they want out of this collection. It tends to be less common but it's still very powerful to use for restricting access. And then for writing, you can also say, I'm gonna allow create which is very helpful for first time creation of documents, and then you can say, I'm gonna allow only updates or I'm only gonna allow deletes if certain things happen.
[00:06:50] And these things are lumped into writes and just like the get in the list that's lumped into reads. So I can say, allow read or write if something happens. So, what do I care about happening? Well, I know who the user is because I've captured their variable of who they are in this uid.
[00:07:11] But in order to restrict, I need to know who is the authenticated user, because anyone could try to access that / user / 123. But only want user 123 to be able to read their data. So to get the currently authenticated user, I can have access to special variables that exist within security roles.
[00:07:31] And there are a ton of these things. The most important one that you're probably gonna use is the request object. And the request has all different sorts of things. I can say, hey, get me the request that authenticated user and then I can get their uid. I can even tap into their token and potentially see what their email is, is their email verified, potentially do they have a phone number, all these types of things and I can even write rules based upon that.
[00:07:59] But I'm going to say, if request that uid is == uid. And so now if anyone who's not that authenticated user, they can't read or write to their profile. That's great, but how do I know to feel comfortable about this? I just wrote something in a new language I haven't seen before, using some variables I've never seen before, I like some sort of assurance that my data is secure.
[00:08:27] For I can use this rules playground and this says, I haven't even published my changes, but it will run some tests and show me whether the read or write was allowed. So in this case let's say, I want to get users david_ 123, and I'm not authenticated. So I'm not gonna check this.
[00:08:48] And I click run, it says, no, it can't. And it shows me right on the line, it says, no value error. And that's because the user does not exist. You can do some no coalescing and say, hey, is off not equal null as well, if you don't like that it has a null value error.
[00:09:09] But also what's really great about the rules playground is, as we hover over these things, it tells us a lot more information, and we can see that off was null, so that was through a null error for the uid. And we can even see the uid was david_123.
[00:09:25] So it is a really nice way to be very interactive with what's going on with your rules. So now lets authenticate, I'm going to give myself a uid of oavid_123, and I think that's all I need, yep. And then so now dismiss and then when I run, hey, look at that.
[00:09:42] It allowed it, and it showed right on the line. And again, I can check to see, okay, my uid was david_123, and I can even see the result of this operation right here. Yeah, that evaluates out to true. I guess that makes sense because that's david_123. So this right here is the most common security rule you'll see.
[00:10:04] And so much so, that what I like to do with security rules is, I like to write it as a function. Cuz security rules you're gonna be doing things over and over again. You can imagine that you're gonna want to check this, so you can write a function called, IsUserOwned, and you can just rereturn.
[00:10:25] From within here, you can say return if request.auth.uid. You can return from within here, and then you can say, is user owned and then pass the new id. I needed to know I needed to pass the uid into here because, yeah, you need to know the scoping of that variable.
[00:10:44] And so now, this is great. And then anywhere else along the path so if I wanna do the same thing with expenses I can say, match expenses of expenseid and I can say allow write, read if is user owned. And the interesting thing here is I'm passing in uid.
[00:11:10] Well, what's the uid for an expense? We don't have the uid at the pack level, how do we access that? Well, we can also access the resource that lives at that path. So in this case I can say, hey, allow let's just simplify this to read for now, allow read if the resource that exists at that location, if it's data property of .uid is that user.
[00:11:44] Then it will say, is the current authenticated user equal to that user? And so the same thing here, if I say, I would need to know data that exists. So I can come out here, let's go find a document of expenses. So let's just grab the first one, has a uid of here, and so I'm gonna wanna get that, right, where is my, here it is.
[00:12:12] I'm gonna say, let's get expenses of, where is the uid, it's in david_123, I'm gonna paste in this uid right here, so I copied from there. And then I'm also going to copy the document id So now if I try to get this document id and I run against that, hey, we can see that it allowed it because the resource is that whole document right here and it has the uid right down here.
[00:12:51] So that is a really powerful thing about security rules is that, you can access data at that resource and tap into its properties and do all these powerful expressions. I would say 50% of the time, this is a lot of the rules you're gonna write, cuz you wanna make sure that that those rules are secure for those specific users.
[00:13:12] And at this point this would actually be a fairly secure app if I went off and I published it. But what we'll get into is that, modifying all of these rules in one little text editor and a console seems kind of bad for collaborative development. So suppose we're going on a team where we would say, I'm the only person that can edit the file, or I'm the only person who can edit the file on Tuesdays cuz I don't want any problems to happen.
[00:13:36] So we have a whole development process where you can write your rules locally, test them against the emulator, and you can even write unit tests which are more like integration tests that make sure that any changes that you have made to your rules do not break. And then from there, you can even deploy your rules with the Firebase CLI and keep your rules stored in source control where they should be.
[00:13:57] So this is a nice way to test but this is not the recommended way for production