0

Based on what is written here, they are "impersonating a user" with a new Firebase admin app instance, so that when they do a write/read the security rules will be triggered for that user.

https://firebase.google.com/docs/database/extend-with-functions

I'm trying to do the same thing but for Firestore, but it doesn't seem to work.

Goal

I have reading/writing to firestore go through an express backend hosted as cloud function. I do not want to implement manual security rules in that backend.

I want to have the firebase-admin app instance to behave "as a user" so the firestore security rules are triggered appropriately, and I can unify my security in the security rules.

Here is the code I'm using:

1. get the admin app options

export function getLocalAdminAppOptions(): admin.AppOptions {
    // we are running locally
    const serviceAccount: admin.ServiceAccount = {
        clientEmail: CLIENT_EMAIL,
        projectId: PROJECT_ID,
        privateKey: (PRIVATE_KEY || "").replace(/\\n/g, "\n")
    }
    return {
        credential: admin.credential.cert(serviceAccount),
        databaseURL: DATABASE_URL
    }
}

2. add the impersonation options & instantiate admin app

import * as Admin from "firebase-admin"
import * as Functions from "firebase-functions"

const appOptions = getLocalAdminAppOptions()
const token: Admin.auth.DecodedIdToken = await Admin.auth().verifyIdToken(req.idToken)
const authOverwrite: Functions.EventContext["auth"] = {
    token,
    uid: req.authUser.uid
}
appOptions.databaseAuthVariableOverride = authOverwrite
const uniqueAppName = "adminAppAsUser:" + req.authUser.uid
const adminAppAsUser = Admin.initializeApp(appOptions, uniqueAppName)

3. make a firestore call with the impersonation app instance

const snap = await adminAppAsUser
  .firestore()
  .collection(`test`)
  .doc("test")
  .get()

return snap.data()

The Problem:

The adminAppAsUser seems to still be a admin app with full access rights, ignoring the security rules.

Bad workaround

I did manage to find a "hacky" workaround: Create a new instance of Firebase for the web inside my backend, and signing with custom token, and use that to access firestore.

Problems with this workaround: signInWithCustomToken takes 800ms every time I run the cloud function!

Code for the workaround:

const uniqueAppName = "adminAppAsUser:" + req.authUser.uid
const adminAppAsUser = Firebase.initializeApp(configs.dev, uniqueAppName)
const token = await Admin.auth().createCustomToken(req.authUser.uid)
await adminAppAsUser.auth().signInWithCustomToken(token)
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
mesqueeb
  • 5,277
  • 5
  • 44
  • 77

1 Answers1

2

I'm trying to do the same thing but for Firestore, but it doesn't seem to work.

It's not supported at all. The impersonation you can get with RTDB does not work for Firestore.

When using any of the backend SDKs for Firestore, the privileges of the service account used to initialize it will always bypass security rules and depend on Google Cloud IAM permissions instead.

See also:

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • Hi @Doug Stevenson! Thanks for your answer. Is there any suggestion you have how to best manually implement security rules for the backend in this case? Writing everything manually with functions seems prone to errors. Is there some sort of library you know which can convert Firestore security rules into something we can execute easily on the backend with the Admin-SDK? – mesqueeb Aug 07 '20 at 03:10
  • No, it's up to you to implement whatever checks you require in your backend code. – Doug Stevenson Aug 07 '20 at 03:26
  • thanks for your comment! I wonder, if I use the Firebase web SDK in the backend, and I authenticate it via `signInWithCustomToken` it takes 800ms. Is there any way to speed this up? (because the web SDK _does_ work with security rules ; ) ) – mesqueeb Aug 07 '20 at 04:06
  • It sounds like you have a new question to post. – Doug Stevenson Aug 07 '20 at 04:19