firebase-functions: Include `context.auth` for Firestore triggers

Version info

firebase-functions: ^2.0.2 firebase-tools: ^4.0.2 firebase-admin: ^5.13.1

Test case

functions.firestore
    .document('test/model')
    .onWrite((data, context) => {
       expect(context.auth.uid).toEqual('the-user-id')
    })

Steps to reproduce

Here’s a test case at brianmhunt/bug-reports on the firestore-functions-auth branch. Just clone, yarn install and yarn test (or npm ^^).

Note that the test case is not testing production behaviour, but the firebase-functions-test behaviour, however I would expect the test and production behaviour to be fixed in tandem.

Expected behavior

The EventContext for a Firestore call should contain the user authentication information, as it does with Callable and Realtime database calls, i.e. context.auth should be populated.

Actual behavior

The context.auth is undefined for firestore onWrite and related functions.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 74
  • Comments: 47 (6 by maintainers)

Most upvoted comments

Hi there again everyone - another update. It has been decided that unfortunately native support for context.auth for Firestore triggers will not be implemented due to technical constraints. However, there is a different solution in the works that hopefully will satisfy your use case, but I cannot share details. On this forum we generally keep open only issues that can be solved inside the functions SDK itself - I’ve kept this one open since it seemed important and I wanted to provide some updates on the internal bugs tracking this work. Now that a decision has been reached, I’m going to close this out. Thanks again for everyone’s patience and I’m sorry I don’t have better news. Please use the workaround referenced in https://github.com/firebase/firebase-functions/issues/300#issuecomment-428593485.

Hi this feature is a deal breaker for me, no concept of who made an update inside the trigger is an oversight, at present this hinders our ability to select firestore as a platform, I look forward to hearing about this feature coming available. asap

Hi everyone, thanks for your patience! This feature request is unfortunately more involved than we originally thought, and will take longer to implement. However, we are doing our best to move this forward.

Internal bug reference: 68792946

Hi, this is a good feature request, and it is on our backlog.

Hi, would really appreciate an update on this, seems like a pretty important feature. Cheers.

What’s the status on this feature?

Hi everyone, I checked back in on this issue to see how it’s progressing. Still blocked, but I linked to this thread and urged the priority of this issue to be increased. Let’s see how it goes. Will post back here with updates when I have them. Thanks for your patience!

@adamduren it wasn’t mentioned because the scope of the requirement is bigger than just user-agent validation/authorization checks.

Consider the following example: I have an object A that can be accessed by a team of users. It is important in such a system to keep a record of who deleted object A. Ideally you’d be able to write an onDelete trigger to add a record - personB, deleted object X, datetime etc.

However, since we don’t have access to who the actor is in triggers… we’d have to do this client side for every delete action. This means if you write a web app and a mobile app, you’d have to duplicate the logic in both clients to log the actors, rather than using the trigger to take care of it.

Just ran into this issue while implementing Firestore Cloud Functions.

@thechenky Is there an update about the different solution?

Thanks!

This is not good. It makes what I would consider a whole class of functions (audit logs, history, etc.) impossible. None of the proposed solutions are clean either. I guess we can just grit our teeth and make a REST API out of other functions. It really does feel like I have just stepped in a dirty puddle on the otherwise nice path that has been my journey with Firebase

Hey @laurenzlong just wanted to follow up on this and see if there’s any progress or if there’s anything the community can do to help move things along. I’d love to move some projects over to firestore but not having this auth data in cloud functions makes it impossible to handle some security setup I use. I’d be happy to take a look and open a PR if there was some direction on what needed to get going. Thanks in advance.

Thanks for the update @thechenky. I can only imagine the the design constraints for a system like Firestore and the challenges that present for the issue here, and while this didn’t get resolved the way everyone hoped I am grateful for the effort to resolve this issue and keep everyone informed.

Summary of how I solved this / a workable solution similar/same as (?) Comment #300:

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:

  1. 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)
  })
  1. 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.

Is there any workaround for this feature? We really need this feature to compensate user some reward whenever a user creates a new record.

It’s already documented in the official reference: https://firebase.google.com/docs/reference/functions/functions.EventContext#.auth

@adamduren yeah, we actually went that route but it has a lot of disadvantages. Some you identified, others:

  1. http function calls are significantly slower than firestore commits
  2. This would render firestore’s offline persistence useless since delete calls would necessarily need to make a server round trip for all deletes.

I’m not exactly happy this feature never made it.

How would you handle logging who deleted a document?

The only hacky workaround I can think of, is having a deleted field on the document, which the client filters out, and then have the onUpdate trigger log that the document was deleted, and then actually delete() the document.

Hideous.

@thechenky another use case which I can’t find an easy workaround: when I need to know which user deleted a document.

For create and update triggers, I can add a field for identifying the user who made a change. However, that’s not possible when deleting a document. Or am I mistaken? Is there a way to know which user deleted a document?

Not having this feature is really frustrating. I hope you reconsider and implement it in future versions.

@thechenky Hi! You mentioned there would be a different solution that might be able to satisfy our use cases. Was it implemented already?

I have a use case where

  • Multiple users can edit a document;
  • When they do it, a Cloud Function adds their changes to a revisions collection including their uid;

Currently, I have to add a latestEditor field including the user’s UID but it would be much easier to just have the context.auth option to get the UID.

Surprised I didn’t see it mentioned in this thread, but if you have to do user-agent validation/authorization checks, that logic can be managed within Firestore rules, which does have access to the auth context

@mqln unfortunately this issue is blocked on a list of other issues that need to be resolved before this is implemented.

Hello there, I created a Firestore trigger that logs write actions into a separate history collection, and the only thing missing is a safe way to identify the user. I hoped the EventContext.auth field would work while the client is signed in with a JWT. The only solution I found yet is to require a user ID in Firestore Security Rules and compare it with the JWT, then the value passed to the trigger would be safe. But even if the trigger can get the user ID and put it into its logging collection, it can’t remove it from the targeted collection without performing a new request, which will, of course, cost something.

I was developing a tracking system about who made a difference for a specific document. I guess there are use cases where we cannot find a programmatical workaround.

@thechenky Thanks for the info. At least, the community would be sure that this feature eventually will be shipped.

Is there any workaround for this feature? We really need this feature to compensate user some reward whenever a user creates a new record.

I ended up doing this for now: https://stackoverflow.com/a/50842161/637751