firebase / firebase-functions

Firebase SDK for Cloud Functions
https://firebase.google.com/docs/functions/
MIT License
1.01k stars 201 forks source link

When crating a user, beforeSignIn may be called multiple times with uids not corresponding to firebase users #1511

Open bragma opened 5 months ago

bragma commented 5 months ago

[REQUIRED] Version info

node:

v21.5.0

firebase-functions:

v1

firebase-tools:

13.0.2

[REQUIRED] Test case

Sample Firebase Auth beforeSignIn function code:

"use strict";

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

admin.initializeApp();

exports.beforeSignIn = functions.region("europe-west1").auth.user().beforeSignIn(async (user, context) => {
  console.log(`SignIn user id ${user.uid}`);
});

exports.beforeCreate = functions.region("europe-west1").auth.user().beforeCreate(async (user, context) => {
  console.log(`Create user id ${user.uid}`);
});

[REQUIRED] Steps to reproduce

Create a Firebase Auth service and add blocking "beforeCreate" and "beforeSignIn" functions. Issue multiple createUserWithEmailAndPassword requests with the same email in parallel.

[REQUIRED] Expected behavior

  1. Even if multiple createUserWithEmailAndPassword calls are issued with the same email, only a single user is created with the specified email
  2. When creating a new user, beforeSignIn is called once, with the uid of the user created in point 1

[REQUIRED] Actual behavior

  1. Even if multiple createUserWithEmailAndPassword calls are issued with the same email, only a single user is created with the specified email
  2. beforeSignIn is called multiple times, with different uids. Only one of the uids matches the uid of the user created in point 1. As a side note, beforeCreate is called multiple times too but this is expected.

Background info: when a user is created via firebase auth, I also need to create an account on my backend. This is done with an HTTP call to my API issued in the beforeCreate blocking function. Due to the way Firebase Auth UI works on iOS, it may happen that a user taps the "create" button multiple times while previous creation request has not returend yet. This causes the SDK to call createUserWithEmailAndPassword multiple times and beforeCreate is called multiple times for the same email/user creation request. The result is that a single user is created on firebase auth, but multiple users are created in my backend (since they have different firebase uid).

So I decided to move the creation of the user in the backend from beforeCreate to beforeSignIn blocking functions. I expected that "beforeCreate" might be called multiple times, since as the name implies the user does not exist yet in firebase when it is called. But I was expecting "beforeSignIn" to be called only once, since documentation states that the flow is:

  1. beforeCreate is called
  2. user is created in firebase
  3. beforeSignIn is called

Problem is that beforeSignIn is called for all "tentative" uids. Only one of those ids actually matches the user created in firebase.

Were you able to successfully deploy your functions?

Yes, problem is not deploy related.

google-oss-bot commented 5 months ago

I found a few problems with this issue:

Berlioz commented 5 months ago

Typically, the correct approach is to prevent multiple create attempts from being made, by disabling the button after the first press or something. It seems like you're using FirebaseUI though, and it does look like there's an outstanding bug for this behavior in that repository (https://github.com/firebase/FirebaseUI-iOS/issues/1169) so it might be on that repository's maintainers to fix this one. We've raised it internally in the hopes of getting some eyeballs on the issue.

bragma commented 5 months ago

I agree that the UI button should be blocked, but a client side solution for a problem that shows up in the server is often a clunky solution. So, point n.1 UI should be fixed. But what puzzles me is why the beforeSignIn function is called with a user that will never be created. I was kind of expecting this for a beforeCreate, but not at signin, considering that on Firebase Auth a single user is created without duplicates.

Let me reiterate on this: if the UI button is pressed multiple times (ex: 3 times):

  1. 3 different "tentative users" will be created
  2. beforeCreate will be called 3 times, one for each different user
  3. Only one of the 3 different users will actually be stored and accepted for Firebase Auth
  4. afterSignIn is called 3 times with the 3 tentative users, knowing from step 3 that only 1 of those will actually be a valid user. 2 signin of those will actually fail.

Considering this is a signin, 4 does not make any sense.