skip to Main Content

To add/update/delete a record in Firestore, I can either run code within my app that executes this operation or I can call a cloud function that will handle the operation. Is it "safer" for my app to do it the cloud function way? It takes a bit longer this way, but I worry about Firebase being vulnerable somehow. I once read that to make a really safe app, you should assume that some malicious user has already reverse engineered your app. Should I assume that using Cloud Functions for all changes to Firestore is the safest route or is there no difference?

Using code from the client:

CollectionReference users = FirebaseFirestore.instance.collection('users');

Future<void> addUser() {
  // Call the user's CollectionReference to add a new user
  return users
      .add({
        'full_name': fullName, // John Doe
        'company': company, // Stokes and Sons
        'age': age // 42
      })
      .then((value) => print("User Added"))
      .catchError((error) => print("Failed to add user: $error"));
}

Using Cloud Functions:

exports.createUser = functions.https.onCall(async (data, context) => {
  const uid = context?.auth?.uid
  if (!uid) {
    return {result: 0, message: 'You are not authorized.'}
  }

  const username = data.user
  const email = data.email  
  var result: number 
  var message: string 

  //Check to see if that username already exists
  const doesUserExist = await TS_doesUserExist(username)
  const createUser = await TS_createUser(uid, username, email)

  if (doesUserExist[0] == 0) {
    if (createUser[0] == 1) {
      result = 1
      message = ''
    } else {
      result = 0
      message = createUser[1]
    }
  } else {
    result = 0
    message = doesUserExist[1]
  }

  return {result: result, message: message}

})

async function TS_createUser(uid: string, username: string, email: string): Promise<[number, string]> {
  var result: number = 0
  var message: string = ''

  //Create SECURE_userinfo doc
  const userInfoDoc = await admin.firestore()
                        .collection(usersCollPath)
                        .doc(uid)  // use uid as document ID
                        .collection(secureUserInfoCollPath)
                        .doc()
                        .set({
                          createDate: admin.firestore.FieldValue.serverTimestamp(), //signupdate
                          email: email,
                        })

  //Create stat doc  
  const statDoc = await admin.firestore()
                  .collection(usersCollPath)
                  .doc(uid)
                  .collection(statCollPath)
                  .doc()
                  .set({
                    stat: 1,
                  })

  //Create username doc
  const usernameDoc = await admin.firestore()
                      .collection(usersCollPath)
                      .doc(uid)
                      .collection(usernameCollPath)
                      .doc()
                      .set({
                        username: username,
                      })

  if (userInfoDoc) {
    if (statDoc) {
      if (usernameDoc) {
        result = 1
        message = ''
      } else {
        result = 0
        message = 'Could not create username doc'
      }
    } else {
      result = 0
      message = 'Could not create status doc'
    }
  } else {
    result = 0
    message = 'Could not create user info doc'
  }  

  return [result, message]
}

2

Answers


  1. It depends on what you mean by "safe". Putting your database logic behind a function just makes an attacker use the function for abuse rather than the database interface. They are both perfectly usable attack vectors.

    The main difference between direct database access (with security rules) vs cloud functions is that you have more expressive power on what inputs to check in the function, since you have a full power of JavaScript at your disposal. This gives you more options than security rules, but is not necessarily "safer" in any case.

    Login or Signup to reply.
  2. I agree with the answer from Doug Stevenson. It also depends on how often you are calling the operations in your app and how important speed is to you or your users.

    I have an established Flutter app that mostly calls Firestore directly using Flutterfire but in some cases calls a cloud function. There is a noticeable delay when calling a cloud function for the first time if it is has not been recently called by the same user or another user.

    I would recommend using direct Firestore access if:

    1. Speed matters to your user experience; and
    2. You can implement effective security rules to protect against risks

    As Doug noted, both direct access and access via a cloud function still present vulnerabilities that need to be managed.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search