Summary of how I solved this / a workable solution:
On client
Add logged in/current user's uid (e.g. as creatorId
) to entity they're creating. Access this uid by storing the firebase.auth().onAuthStateChanged()
User object in your app state.
In Firebase Firestore/Database
Add a Security Rule to create
to validate that the client-supplied creatorId
value is the same as the authenticated user's uid; Now you know the client isn't spoofing the creatorId
and can trust this value elsewhere.
e.g.
match /entity/{entityId} {
allow create: if madeBySelf();
}
function madeBySelf() {
return request.auth.uid == request.resource.data.creatorId;
}
In Firebase Functions
Add an onCreate
trigger to your created entity type to use the client-supplied, and now validated, creatorId
to look up the creating user's profile info, and associate/append this info to the new entity doc.
This can be accomplished by:
Creating a users
collection and individual user
documents when new accounts are created, and populating the new user
doc with app-useful fields (e.g. displayName
). This is required because the fields exposed by the Firebase Authentication system are insufficient for consumer app uses (e.g., displayName
and avatarURL
are not exposed) so you can't just rely on looking up the creating user's info that way.
e.g. (using ES6)
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
const APP = admin.initializeApp()
export const createUserRecord = functions.auth.user()
.onCreate(async (userRecord, context) => {
const userDoc = {
id: userRecord.uid,
displayName: userRecord.displayName || "No Name",
avatarURL: userRecord.photoURL || '',
}
return APP.firestore().collection('users').doc(userRecord.uid).set(userDoc)
})
- Now that you have a validated
creatorId
value, and useful user
objects, add an onCreate
trigger to your entity type (or all your created entities) to look up the creating user's info and append it to the created object.
export const addCreatorToDatabaseEntry = functions.firestore
.document('<your entity type here>/{entityId}')
.onCreate(async (snapshot, context) => {
const userDoc = await APP.firestore().collection('users').doc(snapshot.data().creatorId).get()
return snapshot.ref.set({ creator: userDoc.data() }, { merge: true })
})
This clearly leads to a lot of duplicated user info data throughout your system -- and there's a bit of clean up you can do ('creatorId` is duplicated on the created entity in the above implementation) -- but now it's super easy to show who created what throughout your app, and appears to be 'the Firebase way'.
Hope this helps. I've found Firebase to be super amazing in some ways, and make some normally easy things (like this) harder than they 'should' be; on balance though am a major fan.