cultureshock
cultureshock

Reputation: 31

Run Cloud Function to store Firebase Auth'ed user data to Firestore

I've been trying to create a front-end app with Firebase Authentication (Google sign-in) and Firebase Cloud Function which stores the authenticated user data to Firestore.

  1. A user clicks a button to sign in with Google (Firebase Authentication).
  2. The front-end calls Cloud Function (saveAuthUserData) after sign-in.
  3. Cloud Function runs to store the authenticated user's profile in Firestore (collection: users).

The expected Cloud Function (3) is to send an HTTP POST request to Firestore with the user's profile data.

I wrote this public/index.html and functions/index.js (below), but still looking for the way how it should interact with Firebase without any errors, specifically how to send an HTTP POST request from public/index.html to Cloud Function in functions/index.js. Both files are deployed on Firebase Hosting.

public/index.html

import { initializeApp } from "firebase/app";
import { getAuth, signInWithPopup,GoogleAuthProvider } from "firebase/auth";
import { getFirestore, collection, doc, setDoc } from "firebase/firestore";
import { getFunctions, httpsCallable } from "firebase/functions";
const config = {
    apiKey: "xxxxxx",
    authDomain: "yyyyy.web.app",
    projectId: "zzzzzz"
};
const app = initializeApp(config);
const db = getFirestore(app);
const provider = new GoogleAuthProvider();
const auth = getAuth();
const functions = getFunctions();
var btn = document.getElementById("#sign-in-with-google");
btn.addEventListener("click", function() {
    signInWithPopup(auth, provider).then((result) => {
    var credential = GoogleAuthProvider.credentialFromResult(result);
    var user = result.user;
    if(user !== null) {
        var profile = {
            email: user.email,
            name: user.displayName,
            uid: user.uid
        };
        const setAuthUserData = httpsCallable(functions, "setAuthUserData");
        setAuthUserData(profile).then((result) => {
            console.log(result.data);
        });
    }
});

functions/index.js

const admin = require("firebase-admin");
const functions = require("firebase-functions");
admin.initializeApp();
const fireStore = admin.firestore();

const express = require("express");
const axios = require("axios");
const cors = require("cors");
const app = express();
app.use(cors({ origin: true }));

const setAuthUserData = async (profile) => {
    var isAuthUserSet = false;
    const db = fireStore.collection("users");
    const email = profile.email;
    if(email) {
        const ref = await db.doc(email).set(profile).then(() => {
            isUpdated = true;
      });
    }
  }
  return isUpdated;
}

app.post("/setAuthUserData/", (req, res) => {
  setAuthUserData(req.params.profile).then((response) => res.send(response));
});

const api = functions.https.onRequest(app);
module.exports = { api };

Updated on 9/6/2022

I fixed my code as below by checking this answer, but I couldn't see any changes on Firestore and any success/error messages on both browser's console and GCP's Logs Explorer.

functions/index.js

const admin = require("firebase-admin");
const functions = require("firebase-functions");
admin.initializeApp();
const fireStore = admin.firestore();

const express = require("express");
const axios = require("axios");
const cors = require("cors");
const app = express();
app.use(cors({ origin: true }));

module.exports.setAuthUserData = functions.https.onCall(async (data, context) => {
  var isUpdated = false;
  try {
      const db = fireStore.collection("users");
      const email = data.email;
      if(email) {
        const ref = await db.doc(email).set(data).then(() => {
          isUpdated = true;
        });
      }
      return isUpdated;
  } catch (err) {
      return err;
  }
});

enter image description here enter image description here

Also, this terminal's screenshot shows how both functions and hosting are deployed.

enter image description here

Just in case, I tried to do the same thing without Cloud Function (as below) and it perfectly works as expected. When a user presses a button, he is asked to sign in with Google and his profile is properly stored in Firestore once authenticated.

public/index.html

import { initializeApp } from "firebase/app";
import { getAuth, signInWithPopup, GoogleAuthProvider } from "firebase/auth";
import { getFirestore, collection, doc, setDoc } from "firebase/firestore";
import { getFunctions, httpsCallable } from "firebase/functions";
const config = {
    apiKey: "xxxxxx",
    authDomain: "yyyyy.web.app",
    projectId: "zzzzzz"
};
const app = initializeApp(config);
const db = getFirestore(app);
const provider = new GoogleAuthProvider();
const auth = getAuth();
const functions = getFunctions();
var btn = document.getElementById("#sign-in-with-google");
btn.addEventListener("click", function() {
    signInWithPopup(auth, provider).then((result) => {
    var credential = GoogleAuthProvider.credentialFromResult(result);
    var user = result.user;
    if(user !== null) {
        var profile = {
            email: user.email,
            name: user.displayName,
            uid: user.uid
        };
        (async() => {
            try {
                var profile = {
                    email: user.email,
                    name: user.displayName,
                    uid: user.uid
                };
                const ref = await setDoc(doc(db, "users", user.email), profile);
            } catch (err) {
                console.error(err);
            }
        })();
    }
});

Updated on 9/7/2022

I fixed my code and finally it's working! The reason why it hadn't been working is a few syntax errors in mine, and there is no problem in what's suggested here.

Upvotes: 1

Views: 985

Answers (1)

Renaud Tarnec
Renaud Tarnec

Reputation: 83163

Since you do

const setAuthUserData = httpsCallable(functions, "setAuthUserData");
setAuthUserData(profile).then((result) => {
        console.log(result.data);
    });

in your front-end, you need your Cloud Function to be of type Callable;

Something along the following lines:

exports.setAuthUserData = functions.https.onCall(async (data, context) => {

    try {
         console.log(JSON.stringify(data));

        // Do whatever you need with userData, e.g. write to Firestore
        const db = fireStore.collection("users");
        const email = data.email;

        if (email) {
            const ref = await db.doc(email).set(data);
        };

        return { isUpdated: true }
    } catch (error) {
        console.log(error);

        // Don’t do return error but look at the below URL for exact way to send the error to the front end
        // See https://firebase.google.com/docs/functions/callable#handle_errors
    }

});

Upvotes: 1

Related Questions