This is a quick explanation on how I structured my NoSQL database in Firebase's Firestore in a way that allowed me to secure my user's data and not allow the user to change certain data in a simple photography commerce site. I am a newbie programmer, so Firebase's Firestore was a great solution for me due to its simplicity and the quality of documentation and YouTube tutorials. Speaking of YouTube tutorials, this is just scratching the surface of what Mr. Todd Kerpelman discusses in the YouTube series Get To Know Cloud Firestore. If you want a description on how a person who has been programming for three months utilized the videos to secure their database, read on! If you want a detailed discussion, view the videos because they rock.
- This simple discussion will hit the following topics:
- Data Structure
- Firebase Security Rules and Testing (Super Awesome Stuff!)
- Firestore allows you to store a Collection on the root level, then Documents in each sub level. A document can either store an object or a Collection. This means that each odd node on your database is a collection and each even node is a document. Does that confuse you? If so, let me show you a simple get request that pulls from a User's collection, then checks the orderData subcollection.
- After the first time you authenticate on my site, a Document is created under your specific User ID that is generated by the Firebase Auth system. I'll go ahead and show you what is created when an anonymous user logs in for the first time. The items that are in your cart are set in the "images" document and your "userInfo" is set with an empty object for previous orders and default address.
- As you step through my site, you will be adding images, entering a shipping address (if you are following along, feel free to enter a fake shipping address, make me laugh), and creating an order. This is just a portfolio project, so it doesn't create an order, it just creates a random 5-digit order ID to simulate creating an order using the Pwinty API. All this data is stored in the CurrentOrder subcollection. There are a couple of documents that are created here, such as images (aka the cart), shipmentInfo (aka shipment information), and the orderData (aka where I store any returns from Firebase Functions or API call, like a pwintyId). Remember that orderData is only stored from Firebase Functions, that information will be important to remember when I get to security rules. Here is how the data structure looks when you are finished:
- Once you "Promise You'll Pay", the relevant data for your order is sent to the Previous Order's page via Firebase Functions and Firebase Functions resets the Current Order information, which includes deleting the shipmentInfo and images portion of the db. The 5-digit order ID is used as the key for this document object. I'll let you take a look at the db data of previousOrders after I complete an order:
- My app has a listener for previous orders that updates the state of the app's Account page on any update to a user's Previous Orders on the database. Here is the code and the Account page after completing this example order:
- I hope this shows you how I structured my data. I will now be delving in how I secure this data!
Security Rules and rule testing:
- Security rules are significantly more robust than what I present here. As I noted earlier, the Firestore Security Rules video is super rad and is a must view for any Firebase developer.
- I'll start off by showing you all my security rules, then I will discuss each specific rule.
- Lets start at the top and discuss the prices and skus. The price data and sku data are required to create any order, so we need to be able to read this information before the user logs in. This data is updated through Firebase Functions, so we don't want to allow the client to do any writes or updates. This is why we only allow reads. To make sure we can't update the prices, lets enter the simulator and see what happens when we simulate an update on the prices. We get a friendly "Simulated Write Denied" message, phewwwwww. We also want to make sure we can run a get command, so lets test that!
- Lets think back to the PreviousOrders document we discussed earlier. Once a user is authenticated in our site, we create a blank previous order object for their account. As you see in the rules, we allow read and create, but all updating is done through Firebase Functions. Future versions of this site will likely move this to Firebase Functions, we don't want to give any leeway to the client to create a PreviousOrders object populated with fraudulent previous orders, but I wanted to keep it for this write-up. I have boxed the simulated userId, which I have also entered in the userId authenticate section.
- Now lets look at the CurrentOrders/orderData. As I noted earlier, the orderData is information we receive from Firebase Functions API calls, such as the Pwinty Order Id. We don't want the client to be able to do anything but read this data. If you look at the fifth rule, though, it allows any read or write to any document in the CurrentOrder collection. What do you think will happen if we try to update the orderData? Let's run a quick update test to see.
- Oh no! The user is allowed to update the orderData information. How can we fix that? By disallowing any writes on orderData. Lets look at it again after adjusting the rules. I have added line 14 to not allow any writes, as well as updated the wildcard in line 16 and boolean statement in line 17 to make sure that the orderData is not the document that is updated. Other orderData, such as shipmentInfo and images can still be created and updated in the client app. This still allows the client to read orderData.
- We removed the wildcard in line 16, but we want to make sure shipmentInfo and Images can be created and updated. Lets run a test to make sure. Wahoo! It works.
- And lets look at the Default Address to make sure the user can read, create, and update their default address.
- Now we want to make sure mal1 can't access uid1's sweet data and lets use wildcards in the simulator!
That wraps up the simple security rules that I have created for my app. Thanks for stopping by!