Properly securing RT Database when using Cloud Functions, Admin SDK, and an open API

125 views
Skip to first unread message

Michael Leggett

unread,
May 27, 2021, 5:33:53 AM5/27/21
to Firebase Google Group
Help!

I'm trying to create an endpoint that anyone can call to register the start of a trial. But I only want to allow new trials to be recorded, no edits or deletes on existing trials.

I'm using a realtime database and cloud functions. I mostly have this working. 
  • I made a node in my database to keep track of when trials start: { trials: { emailHash: timestamp } }
  • I made a cloud function with an express app so I can do a POST with fetch to record when a trial starts (region-project.cloudfunctions.net/function/emailHash)
  • This function correctly records the trial start date.
  • I then made security rules on my database to only allow a write to { trials } if !data.exists() && newData.exists() to ensure the trial start time is never updated or deleted.
The problem is, the security rule isn't blocking calls to my function.

I think that it is because my Cloud Function is using the Admin SDK and not a service account and the admin can write to the database regardless of the rules? I even tried changing the rules to { "trials": ".write": false } and the writes still went through. 

But as soon as I get into the pit of service accounts, I get lost really quick. I think Firebase auto-created some service accounts when I setup my project. Do I lock those service accounts down? Or maybe I need to call admin.initializeApp() with a config to use that service account? Or do I need to create a new service account? Or is the problem elsewhere entirely?

I'm not trying to get help with my code, but in case it better explains what I'm doing, here is a summary:

const admin = require("firebase-admin");
const functions = require("firebase-functions");
const express = require("express");

admin.initializeApp();
const app = express();
const database = admin.database();

app.post("/initTrial", (req, res) => {
  const emailHash = req.body?.hash;
  const now = Date.now();
  if (emailHash) {
    database.ref(`trials/${emailHash}`).set(now).then(() => {
      res
.status(200).send("Successfully initialized trial");
    });
  }
});

exports.externalCall = functions.https.onRequest(app);

Sam Stern

unread,
May 27, 2021, 10:28:07 AM5/27/21
to Firebase Google Group
Hi Michael,

You are correct. The Admin SDK authorizes as a service account and therefore has full access to nearly all resources in your project and is not subject to security rules. Security Rules only come into play when the request comes from an unauthenticated user or a user authenticated via Firebase Auth.

Cloud Functions is considered a "trusted environment" and therefore has access to your project's default service account automatically. This is why admin.initializeApp() with no further arguments works inside a Cloud Function. If you tried to run that same code on AWS or another non-trusted environment (from Google's perspective) you'd find that you need to explicitly provide a service-account.json file

However you're in luck because Firebase Realtime Database has an "auth override" function in the Admin SDK which you can use to reduce your privileges:
https://firebase.google.com/docs/database/admin/start#authenticate-with-limited-privileges

It would look something like this:
const admin = require("firebase-admin");

// Initialize the app with a custom auth variable, limiting the server's access
admin.initializeApp({
  credential: admin.credential.applicationDefault(),
  databaseURL: "https://databaseName.firebaseio.com",
  databaseAuthVariableOverride: {
    uid: "my-service-worker"
  }
});

// The app only has access as defined in the Security Rules
const db = admin.database();
// ...

Note that Realtime Database is the only Firebase project which has this auth override functionality. If you're talking to any other Firebase product from your server, assume that there are no guard rails.

Hope that helps!
Sam

--
You received this message because you are subscribed to the Google Groups "Firebase Google Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to firebase-tal...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/firebase-talk/e77feb80-eaa2-473a-a078-4b0734f3ba2an%40googlegroups.com.

Michael Leggett

unread,
Jun 1, 2021, 1:59:23 PM6/1/21
to Firebase Google Group
SUPER HELPFUL. I have this working and am grateful to also better understand how all this works. 

Thanks so much Sam!

Reply all
Reply to author
Forward
0 new messages