I’m Building An App – Step 3 – Security

Is Validation Enough?

Since my app is a serverless app, the validation performed on Step 2 is only client-side validation. While this does improve the user experience and helps prevent incorrect information from being entered into the app database, it does not protect against malicious attacks against my database.

Client side security is not security.

– some random person on Reddit

The Conundrum

We know that we need to do some validation/sanitization on the server side, but we don’t have a server. How is this possible?

Enter Firebase Cloud Functions. From the Firebase docs:

Cloud Functions for Firebase lets you automatically run backend code in response to events triggered by Firebase features and HTTPS requests. Your code is stored in Google’s cloud and runs in a managed environment. There’s no need to manage and scale your own servers.

When I first read about Cloud Functions I actually imagined that you would write all of your code using a browser-based GUI or something; I was having a tough time determining how exactly you would write this server-side code. As it turns out, you write all of the code within your own app and then deploy it to Google’s servers.

Let’s dive into it and figure it out.

Add the NPM Module

Importing the Cloud Functions module is pretty simple, it’s basically three terminal commands in your project folder:

npm install -g firebase-tools
firebase login
firebase init functions

After performing these steps, there will be a /functions/ folder in the root of the app. Within that folder’s /src/ folder will be an index.js file. This is where the Cloud Functions code will exist.

The Code

import * as functions from 'firebase-functions';
const sanitizeHtml = require('sanitize-html');

const sanitizeData = ( data:any ) => {
    return sanitizeHtml(data, {
        allowedTags: [],
        allowedAttributes: {}
    })
  }

exports.onContactWrite = functions.database.ref(`users/{uid}/contacts/{id}`).onWrite( (change, context) => {
    const data = change.after.val()
    for (const prop in data) {
        data[prop] = sanitizeData(data[prop])
    }
    return change.after.ref.update(data)
  })

  exports.onMeWrite = functions.database.ref(`users/{uid}/me`).onWrite( (change, context) => {
    const data = change.after.val()
    for (const prop in data) {
        data[prop] = sanitizeData(data[prop])
    }
    return change.after.ref.update(data)
  })

That’s pretty much it for that part. I may beef this up later as I learn more but this gets the job done. Data that is sent with HTML tags will get stripped out. Some examples:

InputOutput
<strong>Thomas</strong>Thomas
alert(“pwned!”)ThomasThomas

What About SQL Injections?

Fair question. I have a database which means I need to protect against SQL Injection attacks, right? What are those, anyway?

SQL injection is a code injection technique, used to attack data-driven applications, in which malicious SQL statements are inserted into an entry field for execution (e.g. to dump the database contents to the attacker).

https://en.wikipedia.org/wiki/SQL_injection

Actually, I don’t.

The Firebase Realtime Database is a NoSQL database environment, meaning that as the developer you are not preparing SQL queries yourself and sending them to the server. Instead, you interact with functions provided by the Firebase codebase which then make writes/reads to the database.

So, the short answer is nah.

Database Permissions

There is, however, a second layer to protecting data within your database, namely, authorization. Put simply, you need to set rules for how users can access and write the data in your database. If you don’t do this, anyone interacting with your app can modify any of the data in your database.

We definitely don’t want that, so, let’s set up some rules. Thankfully, Firebase Realtime Database makes it easy to set some basic rules.

Here’s pretty much as simple as it can get:

{
  "rules": {
    "users": {
      "$uid": {
        ".read": "$uid === auth.uid",
        ".write": "$uid === auth.uid"
      }
    }  
  }
}

This means that within the data contained for a specific user(think back to the hierarchy of our data in the database), that user is only allowed to read and write their own data. Basically, when you are logged into your user account within my app(remember that Auth is powered by Firebase), you can only view and modify the data at or below your user account level.

Larger, more complex apps will for sure need more stringent controls but in my case I think this is sufficient. Technically a user could change anything assigned to their user account which could pose a problem if you stored things like their user privileges, plan data, or renewal dates. If that’s the case, you don’t want people upping their privileges to Admin(if you have user roles) and you definitely don’t want someone changing their renewal date to 50 years from now and getting a free ride to use your app forever.

For the purposes of this simple app, those very simple rules should work, though. As far as I can tell, the worst they could do is spam their own contact data with emojis so I’m fine with that.

Do You Even Test Bro?

You mean, have I written a unit test for this? Why yes, in fact I did, however, Unit Tests are part of Step 4. 🙂

Until next time.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s