hey there , I am currently working as a Android Developer intern at a Company. Currently I am the only techie in the company and i was asked to make an app for the company's founder and i am kind of stuck in an issue from days . I made a project in the firebase (hence i have the Owner role in GCP/Firebase for project) as we have this functionalities in the app which require firebase firestore , storage , and firebase functions . Firebase firestore and Storage is working fine. As soon as i tried to deploy two functions in the firebase/Google Cloud Console , then i was facing difficulty deploying the functions. After some digging i found out that the functions can be deployed only by the Default Compute Service Account for projects which come under organisations (our project was also an organisation level project in both firebase and google cloud , because of creating the project in firebase using the company email address xyz@company.com ) . Knowing this i was able to deploy the functions after few tries, but when i try to trigger them , they are just not getting triggered. I made a HTTP (on Call) testing function named sendHello , which basically send hello from the function (or server) on button click and when i try triggering this function (on button click) , it is giving me UNAUTHENTICATED error ...the other two Primary functions were HTTP (on Request ) function are getting triggered but they are failed to the intended work ..... Due to being stuck for the long time, i migrated from this project to new project (created a new project under normal personal email address firebase account ) and all the functions were running and working as expected.
these are three functions and their respective triggering function in android project in the activity is as follows ....
const functions = require("firebase-functions");
const nodemailer = require("nodemailer");
const admin = require('firebase-admin');
// Initialize Firebase Admin SDK
admin.initializeApp();
// Configure your email transport using the SMTP settings of your email provider
const transporter = nodemailer.createTransport({
service: 'gmail', // Use your email service, such as Gmail
host: 'smtp.gmail.com',
port: 587,
secure: false,
auth: {
user: 'xyz@gmail.com', // Your email id
pass: 'xxxxxxxxxxxxxxxxx', // Your email password or an app-specific password if 2FA is enabled
},
});
// Cloud function to send an email invite to the new co-owner
exports.sendInviteEmail = functions.https.onRequest((req, res) => {
const email = req.body.email; // Get the email from the request body
const granter = req.body.granter; // Get the granter name
const appLink = "https://play.google.com/store/apps/details?id=com.example.abc"; // Link to your app's download page
const mailOptions = {
from: 'xyz@gmail.com', // Sender address
to: email, // Recipient email
subject: 'Co-Owner Access Granted',
text: `You have been made co-owner by ${granter} in ABC app. Kindly download the ABC app from ${appLink}`,
};
// Send the email
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
return res.status(500).send({ success: false, error: error.toString() });
}
return res.status(200).send({ success: true, info });
});
});
//Function to monitor new user account creation
exports.monitorCoOwnerAccountCreation = functions.https.onRequest(async (req, res) => {
const { granter, granterKey, granterName, email } = req.body;
try {
// Set up a Firestore trigger to monitor user creation in Authentication
admin.auth().getUserByEmail(email).then((userRecord) => {
const coownerAccessReceiver = userRecord.uid;
// Save co-owner details to Firestore
const coOwnersUserDetails = {
coownerAccessGranter: granter,
coownerAccessGranterKey: granterKey,
coownerAccessGranterName: granterName,
coownerAccessReceiver: coownerAccessReceiver,
};
firestore.collection("CoOwners")
.doc(coownerAccessReceiver)
.set(coOwnersUserDetails)
.then(() => {
console.log("Co-owner document successfully created.");
res.status(200).send({ success: true });
})
.catch((error) => {
console.error("Error creating co-owner document: ", error);
res.status(500).send({ success: false, error: error.message });
});
}).catch((error) => {
console.log(`User with email ${email} does not exist yet.`);
res.status(200).send({ success: false, message: `No user with email ${email} exists.` });
});
} catch (error) {
res.status(500).send({ success: false, error: error.message });
}
});
// Simple test function to send "Hello" response
exports.sendHello = functions.https.onCall((data, context) => {
if (!context.auth) {
throw new functions.https.HttpsError(
'unauthenticated',
'The function must be called while authenticated.'
);
}
return "Hello from Firebase!";
});
private fun sendInviteEmail(granterUID: String, credential: String) {
val url = "https://xxxxxx.cloudfunctions.net/sendInviteEmail"
val jsonObject = JSONObject().apply {
put("email", credential)
put("granter", granterUID)
}
val requestBody = RequestBody.create(
"application/json; charset=utf-8".toMediaTypeOrNull(), jsonObject.toString()
)
val request = Request.Builder()
.url(url)
.post(requestBody)
.build()
val client = OkHttpClient()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.e("NetworkErrorEmail", "Network call failed", e) // More detailed error log
}
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful) {
Log.d("emailSent","Email sent successfully")
} else {
Log.d("emailSent","Failed to send email: ${response.message}")
}
}
})
}
private fun monitorNewCoOwnerCreation(granterUID: String, granterKey: String, granterName: String, credential: String) {
val url = "https://xxxxxxx.cloudfunctions.net/monitorCoOwnerAccountCreation"
val jsonObject = JSONObject().apply {
put("granter", granterUID)
put("granterKey", granterKey)
put("granterName", granterName)
put("email", credential)
}
val requestBody = RequestBody.create(
"application/json; charset=utf-8".toMediaTypeOrNull(), jsonObject.toString()
)
val request = Request.Builder()
.url(url)
.post(requestBody)
.build()
val client = OkHttpClient()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.e("NetworkErrorMonitor", "Network call failed", e) // More detailed error log
}
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful) {
Log.d("monitorStart","Firebase Function triggered successfully")
} else {
Log.d("monitorStart","Failed to trigger Firebase Function: ${response.message}")
}
}
})
}
private fun sendHelloFunction() {
// Call the 'sendHello' function
functions
.getHttpsCallable("sendHello")
.call()
.addOnCompleteListener { task ->
if (task.isSuccessful) {
// Handle successful response
val result = task.result?.data as? String
Toast.makeText(this, result, Toast.LENGTH_LONG).show()
} else {
// Handle error
Toast.makeText(this, "Function call failed: ${task.exception?.message}", Toast.LENGTH_LONG).show()
}
}
}
I am just getting empty response messages in this two logs ...
Log.d("emailSent","Failed to send email: ${response.message}") and
Log.d("monitorStart","Failed to trigger Firebase Function: ${response.message}")
for sendInviteEmail and monitorNewCoOwnerCreation respectively .... and Function call failed: UNAUTHENTICATED for this toast in sendHelloFunction
Toast.makeText(this, "Function call failed: ${task.exception?.message}", Toast.LENGTH_LONG).show()
Hi @Divyansh-Raj,
Welcome to the Google Cloud community!
The UNAUTHENTICATED error for sendHello
and the failures for sendInviteEmail
and monitorCoOwnerAccountCreation
strongly suggest that the problem lies in how your Android app is interacting with Firebase Authentication and passing those credentials to your functions.
What you should investigate:
sendInviteEmail
and monitorNewCoOwnerCreation
) likely isn't including proper Firebase Authentication. When you make HTTP requests to your functions, you aren't attaching the authentication token that Firebase would expect. sendHello
, being a callable function, is explicitly checking for authentication, and failing because no authentication is passed.You can try below troubleshooting steps and recommendations that could help resolve this behavior:
sendInviteEmail
and monitorNewCoOwnerAccountCreation
, include the ID token in the Authorization header of your HTTP requests. sendHello
, Firebase client SDK (using getHttpsCallable
) will automatically handle authentication if the user is logged in through the Firebase Authentication SDK. No explicit header is needed there. The fact that this is failing means the user is not logged in within the client.If the observed latency is not acceptable for your end, please reach out to Google Cloud Support. Their team has specialized expertise in diagnosing underlying problems. When contacting them, provide comprehensive details and include screenshots. This will help them better understand your issue. However, I can’t give a specific date as to when this will be resolved.
I hope the above information is helpful.
Hello @ChieKo
ThankYou for correctly pointing out the flaws in my code.
After i published this post and before your reply , i got to know that the ID token is required to send the request to firebase HTTP trigger based functions and i rectified my code accordingly.
The android side code is as follows for calling the sendInviteEmail.
//Somewhere in the onCreate of activity
val firebaseUser = FirebaseAuth.getInstance().currentUser
if (firebaseUser != null) {
firebaseUser.getIdToken(true).addOnCompleteListener { task ->
if (task.isSuccessful) {
val idToken = task.result?.token
// Now, send the request with the ID token
sendInviteEmail(firebaseUser.uid, credential, idToken)
} else {
Log.e("ID Token Error", "Failed to retrieve ID token: ${task.exception}")
}
}
}
private fun sendInviteEmail(granterUID: String, credential: String, idToken: String?) {
val url = "https:/xxxxxx.cloudfunctions.net/sendInviteEmail"
val jsonObject = JSONObject().apply {
put("email", credential)
put("granter", granterUID)
}
val requestBody = RequestBody.create(
"application/json; charset=utf-8".toMediaTypeOrNull(), jsonObject.toString()
)
Log.d("BearerToken",idToken.toString())
val request = Request.Builder()
.url(url)
.post(requestBody)
.addHeader("Authorization", "Bearer $idToken") // Add the ID token here
.build()
val client = OkHttpClient()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.e("NetworkErrorEmail", "Network call failed", e) // More detailed error log
}
override fun onResponse(call: Call, response: Response) {
if (response.isSuccessful) {
Log.d("emailSent", "Email sent successfully")
} else {
Log.d("emailSent", "Failed to send email: ${response.message}")
}
}
})
}
and the sendInviteEmail functions code is as follows ...
exports.sendInviteEmail = functions.https.onRequest(async (req, res) => {
const idToken = req.headers.authorization?.split("Bearer ")[1];
console.log("Function triggered, checking authorization...");
if (!idToken) {
return res.status(403).send("Unauthorized");
}
console.log("Token has some value");
try {
// Verify the ID token
const decodedToken = await admin.auth().verifyIdToken(idToken);
console.log('Successfully Verified ID token:', decodedToken);
// Proceed with email logic
const email = req.body.email;
const granter = req.body.granter;
const appLink = "https://play.google.com/store/apps/details?id=com.company.abc";
const mailOptions = {
from: 'xyz@company.com',
to: email,
subject: 'Co-Owner Access Granted',
text: `You have been made co-owner by ${granter} in ABC. Kindly download the ABC app from ${appLink}`,
};
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
return res.status(500).send({ success: false, error: error.toString() });
}
return res.status(200).send({ success: true, info });
});
} catch (error) {
console.error("Error verifying ID token:", error);
return res.status(403).send("Unauthorized");
}
});
As you can see i am sending the token and verifying it in the functions code, but i am still encountering the same problem.
To use this token verification workflow , i think the function resource has to be granted the cloud Run Invoker role for allAuthenticatedUsers Principal.