[MUSIC PLAYING]
RACHEL MYERS: Hi.
I'm Rachel.
SAM OLSEN: And I'm Sam.
Welcome to the next episode in our series
about making your app safe and secure.
RACHEL MYERS: Today we want to give you
the tools you need to start thinking about app security
and user privacy from the very beginning of app development.
SAM OLSEN: Let's review how Firebase Security
is different from security in a traditional app.
In a traditional architecture, there's
a client, a server, and a storage layer.
The server acts as a natural security check.
Because you control the server and the code that's
running on it, it's a trusted environment,
and the server makes all the requests to the database.
In Firebase, there's no server, and the client
connects directly to the storage layer.
You don't control the clients, so those
aren't trusted environments.
So how will you prevent a user from reading all the data,
or overwriting all of it?
How do you enforce that, for example, in this to-do app,
I can only see my to-dos, and you can only see your to-dos?
The answer is security rules.
Security rules let you specify which parts of the Firebase
backend each client can access.
Security rules are executed on every request.
A request from a client will go through security rules.
And if Security Rules says the request is allowed,
the backend will send a response.
Otherwise, they'll get a permission denied error.
RACHEL MYERS: For example, you can write a simple rule that
says signed in users can create a new document if they're
listed as the owner, or you could create a more complicated
rule that a user can only modify a comment they've
made if they have a karma score over 15,
if they made the original comment less
than two hours ago, and if the modified comment is shorter
than the original comment.
You can create all sorts of different conditions
for who can read and edit different kinds of documents.
But whether they are simple or complicated,
it's essential that you write security rules.
Ideally, start writing rules as soon
as you start writing your app, because security rules will
change how you structure your data.
So we'll cover the tools, new and old, that let you do that.
SAM OLSEN: Let's dive into what security rules look like.
First, match statements define which
parts of the backend the following rules will apply to.
For Firestore, the match statements
end at the document level.
We include the collection, and then
make the document ID variable by putting it in curly braces.
This rule will apply to all the top-level documents
in this collection, and the Document ID
will be available within the block.
For storage, the match statements
include bucket names, and end at the file level.
When you're writing rules, you want
to avoid global rules-- rules that match the entire database
or bucket.
Instead, you want to write rules that are tailored
to each kind of document.
Granular rules act like a schema.
You can see what fields you have in each document, what
the types of each field should be, and who has access to it.
Next, we define a specific permission.
The available permissions for Firestore
are create, update, delete, get, and list.
Additionally, read is an alias for
both get and list, and write is an alias for create, update,
and delete.
If you write overlapping rules--
for example, a rule that allows creates and a rule that allows
writes--
if any rule evaluates to true, the user will have access.
Rules aren't filters.
If any rule grants access, access
is granted on the whole request.
A way to avoid mistakes is to have each document match
only one rule.
RACHEL MYERS: Finally, each rule needs a condition.
If you want to lock down your app entirely,
the condition could be as simple as false,
but the options for conditions are limitless.
There are two important objects you'll use over and over again
in the conditions for your security rules.
First, the resource object is the document
that the user is trying to access as it's currently
written in the database.
If this is the create method, the resource object
will be empty If I structured the Firestore
document for an item in my to-do list to have an ownerUID field,
I can write a rule that checks this UID.
I want to compare it to the ID of the user who
is making the request.
For that, I'll need the other important object
in the rules conditions-- the request object.
The request object contains all the attributes
related to the request, including
the path or the query that's being accessed,
the new or updated resource that the request wants to write,
and in the auth object, details about the user who's
making that request.
So I can finish my rule by comparing
the user ID I had in the database
to the ID of the user who's making the request.
These aren't the only types of objects and functions
that you have access to, but these are the most important.
We've linked to the ref docs in the description
so you can dive in more.
SAM OLSEN: And you can use custom functions
and local variables in your security rules.
To make your code easier to read and reduce
duplication, when you have conditions that are repeated
again and again across your rules,
you can pull those into a function.
Instead of repeating this logic, you can now call the function.
Here, I've replaced all the conditions
that made an exception for site admins
with a call to one single function.
Within functions, you can also create local variables
using the keyword let.
This is particularly handy for writing readable code.
This hasn't changed the logic at all,
but it's much more readable.
RACHEL MYERS: That's the syntax of rules.
Next, let's think about what kinds of security
rules that you want to write.
You can make it a lot easier on yourself
if you start thinking about security rules
as soon as you start thinking about the data
that you want to save.
Who should be able to read, create, update, or delete
this final data?
If you're about to save a document where some fields are
public and some are private, use a subcollection
to put them in separate documents.
Remember, Firestore rules apply to documents,
so creating two documents in subcollections
means that you can write two different security
rules for each of them.
Here I've written two security rules--
one for the data that needs to be private,
and one for data that other people in the app
need to be able to read.
We've linked, in the notes, to a video
on how to model your data in Firestore.
SAM OLSEN: Security rules can also contain data validations.
Access control rules are absolutely required,
because they protect your users' data from malicious actors.
By contrast, validations are here
to make your life easier by enforcing the types
and constraints of the data being
entered into your database.
For example, I expect this data to always
be an integer between 1 and 24, so
to prevent any funny business, I make that a requirement
to save the data.
Similarly, here I enforce which fields are required.
There are limits to what you can do with validations and rules.
You'll still want to use client-side validations
for most things as well, because they
will give the user feedback that they need to change something.
And if you're making any writes from Firebase functions
or another trusted environment, the Admin SDK skips
Firebase rules entirely, so make sure that whatever it's writing
matches your validations.
Here I've made the createdAt timestamp and owner immutable.
People can update this document, but if someone
tries to change those particular values, the write will fail.
Map diffs are a relatively new feature to security rules.
They let you compare original values to updated values.
I'm comparing the request.resource.data
to the already-saved resource.data,
and then checking to make sure that my immutable fields are
both unchanged in the write.
Map diffs are great for this kind of thing.
RACHEL MYERS: We know that writing security rules
can feel really high stakes.
You don't want to make a mistake that grants access to someone
who shouldn't have it.
We've created several tools for you
to experiment with, and then even
libraries for you to write automated tests
against your security rules.
The most important tool is the Firebase Emulator Suite,
a local emulation of Firebase backend services.
With emulators, you can test your security rules either
manually, by clicking around, or with automated tests using
the rules unit testing library--
if you're writing tests in JavaScript.
In my automated test, here I've stubbed out
the user attributes I want to test, and then in each test
I make a specific SDK call, and I
declare if that request should succeed or fail.
I've added these tests to my CI setup
to make sure I won't accidentally break my security
rules in the future.
We've included, in the links below,
a video about how to get these kinds of tests set up
to run as GitHub actions.
SAM OLSEN: The Emulator Suite is also
a great place to build and debug without slowing down requests.
Two features worth highlighting are the Debug function
and the Requests Monitor.
The Debug function lets you inspect
any value in your Rules file.
I can call debug on any expression in my rules.
I'll add it here around request.time.
And make sure that it's in the format I expect.
And when I run my test, it prints the standard out.
Requests Monitor is a cool new feature we just released.
It lets you see the requests coming
into the Firestore emulator in real time,
and allows you to drill down into any request.
You can see all the attributes of the request,
and what happened during rules evaluation.
This tool is especially handy when you're writing your rules
and you're not sure which fields the rules have access to.
For example, you can see all the fields
attached to the auth object in request.auth.
When you start building your app,
you can watch the request monitor
and see all the parameters that are coming in as part
of the request, and start writing rules
from the very beginning.
RACHEL MYERS: That's it for this video.
My final tip is that I recently released
a collab to help you get up and running on rules,
and it's linked in the description below.
The collab covers how to think through and write
a variety of rules.
It takes about an hour, and it will really
level you up in how you're thinking about and writing
your security rules.
Sam, what's your final tip?
SAM OLSEN: Let me see.
Security rules are business logic,
like the rest of your app's code,
so it's important you treat them like that.
When you make changes to security rules,
review them like you would the rest of your code.
A link to our guide on how to review security rules
is below the video.
RACHEL MYERS: To recap our tips, avoid global rules.
Rarely do you want one permission for every document
in your database.
Rules aren't inherited through subcollections.
Write separate rules for documents
that are in subcollections.
Rules don't act as filters.
If any rule grants access, that user will have access.
All client requests go through security rules,
but requests from trusted environments use the Admin SDK,
and they bypass security rules.
Avoid duplication, meaning both avoid repeating logic
and avoid overlapping rules.
Use the Emulator Suite and the tools
there for debugging and testing your rules,
like the Debug function and Requests Monitor.
Treat your security rules like the rest of your code.
Test them, code review them, and check them in.
SAM OLSEN: That's all.
Thanks so much for listening in.
We'll see you next time, when Rosalind gives us the lowdown
on Firebase App Check.
[MUSIC PLAYING]