Working serverless with Angular and Firebase is great, but sometimes there’s a requirement that seems hard to achieve. Firebase Storage allows you to set complex security rules to manage access per user, and you can restrict upload size on a per-file basis. But, what if you need to restrict user uploads based on a per-user quota? This can be achieved by using Firebase Cloud Functions and Custom Auth Tokens.

First we define Firebase Storage security rules that allow users to upload to their own private area within the storage. Users can only read files that are prefixed by their user ID. They can only write to paths where their user ID matches AND the file path ( matches the path in the token AND the file size (resource.size) is smaller than the remaining quota specified in the auth token.

The upload process is then achieved in 3 steps:

  1. Call a Firebase Cloud Function to generate a new auth token that includes the user’s remaining quota allowance.
  2. Re-authenticate with Firebase using the custom token.
  3. Upload the file to Firebase Storage.

First, on the Angular side, we wrap the storage upload observable to make it easier to compose. Notice that the upload path is created by combining the user ID and a file ID (generated elsewhere). This path is also generated as part of the token and part of the security rules check that these match.

The three steps to upload can then be composed using RxJS Observables. Notice that the same ID used for the file path is also passed as an argument to the getQuotaToken callable.

For the Cloud Function, we first create a utility function that will take a user ID as parameter, list all the files for this user, and calculate the total sum of the file sizes. This function getUsage will be used by our Cloud Function:

We can now define a Cloud Function that will generate and return the Custom Authentication Token. The Angular front-end code calls this Cloud Function and passes the generated token to Firebase. This token is then used by the Firebase Storage security rules when the file upload is attempted.

We have provided for a default quota, and allow a quota to be specified for each user. The constant DEFAULT_QUOTA should be set to a value in bytes. For example 5MB would be (5 * 1024 * 1024). You could allow users to purchase more storage space, in which case you would set the user’s quota value after a successful payment (using setCustomUserClaims). The example above is using a quota that has been set on the user with the DEFAULT_QUOTA value as a fallback.