firebase / firebase-admin-dotnet

Firebase Admin .NET SDK
https://firebase.google.com/docs/admin/setup
Apache License 2.0
366 stars 131 forks source link

FCM SendAllAsync not working with Google Application Default Credentials #279

Closed jonasgoderis closed 3 years ago

jonasgoderis commented 3 years ago

Environment

The problem

We're developing a .NET Core API for a new mobile applicatione. One of the features is sending Push Notifications from the API. To make connection with all our Google Services and Firebase Cloud Messaging we're making use of APPLICATION DEFAULT CREDENTIALS. This is not done via a environment variable but with logging in with following command.

gcloud auth application-default login

When doing the initial setup we encountered a problem that had the following error message.

FirebaseAdmin.Messaging.FirebaseMessagingException: 
Your application has authenticated using end user credentials from the Google Cloud SDK or Google Cloud Shell which are not supported by the fcm.googleapis.com. 
We recommend configuring the billing/quota_project setting in gcloud or using a service account through the auth/impersonate_service_account setting. 
For more information about service accounts and how to use them in your application, see https://cloud.google.com/docs/authentication/.

Eventually this problem was resolved by setting the billing/quota_project in my config by running following command.

gcloud auth application-default set-quota-project my-project

Sending single push notifications was no problem and everything was implemented very quickly.

Now we were up to sending push notifications to a batch of devices (tokens). Therefore we are using the method SendMulticastAsync to send a single message to different devices, underlying this method is using SendAllAsync.

When we implemented this in our API we noticed that none of the messages was sent. When digging a bit deeper we saw that the reason for this was following error which is the same as we encountered during setup. The strange thing is that the single notification is still working.

FirebaseAdmin.Messaging.FirebaseMessagingException: 
Your application has authenticated using end user credentials from the Google Cloud SDK or Google Cloud Shell which are not supported by the fcm.googleapis.com. 
We recommend configuring the billing/quota_project setting in gcloud or using a service account through the auth/impersonate_service_account setting. 
For more information about service accounts and how to use them in your application, see https://cloud.google.com/docs/authentication/.

I already pulled my hair out finding the solution for this but after hours of searching didn't came up with a solution. No idea if I'm doing something wrong or there's some bug. But I do hope you guys can help out :)

Relevant Code:

// Startup.cs

FirebaseApp.Create(new AppOptions
{
    Credential = GoogleCredential.GetApplicationDefault(),
    ProjectId = configuration.GetValue<string>("Firebase:ProjectId")
});
// Implementation

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FirebaseAdmin.Messaging;
using MyProject.Domain.CloudMessaging;

namespace MyProject.Infrastructure.Firebase
{
    public class CloudMessagingService : ICloudMessagingService
    {
        public async Task SendSingleNotification(NotificationData notificationData, string token)
        {
            if (token == null)
            {
                return;
            }

            var message = CreateMessage(notificationData, token);

            try
            {
                await FirebaseMessaging.DefaultInstance.SendAsync(message);
            }
            catch (Exception e)
            {
                Console.Write(e);
            }
        }

        public async Task SendMultipleNotifications(NotificationData notificationData, List<string> tokens)
        {
            if (tokens.Count == 0)
            {
                return;
            }

            var message = CreateMulticastMessage(notificationData, tokens);

            try
            {
                var response = await FirebaseMessaging.DefaultInstance.SendMulticastAsync(message);
                Console.WriteLine($"{response.SuccessCount} messages were sent successfully");
            }
            catch (Exception e)
            {
                Console.Write(e);
            }
        }

        private Message CreateMessage(NotificationData notificationData, string token)
        {
            var message = new Message
            {
                Notification = new Notification
                {
                    Title = notificationData.Title,
                    Body = notificationData.Body
                },
                Data = notificationData.Data,
                Token = token,
                FcmOptions = new FcmOptions
                {
                    AnalyticsLabel = notificationData.AnalyticsLabel.ToString()
                }
            };

            return message;
        }

        private MulticastMessage CreateMulticastMessage(NotificationData notificationData, IReadOnlyList<string> tokens)
        {
            var message = new MulticastMessage
            {
                Notification = new Notification
                {
                    Title = notificationData.Title,
                    Body = notificationData.Body
                },
                Data = notificationData.Data,
                Tokens = tokens
            };

            return message;
        }
    }
}
google-oss-bot commented 3 years ago

I found a few problems with this issue:

DamienDoumer commented 3 years ago

The recommended ways to send messages to several users are:

This library already implements topic messaging, so you can leverage it as I did here to subscribe users to topics, and here to send a notif to multiple users

But this library doesn't implement device group messaging. So, based on Google's documentation, I made an implementation of Device group messaging in C#. Which is available here, I tested it and it works fine. There is a little API attached to the project, which you can use to test it by yourself.

I also opened an issue here, since I'm planning to make a PR that includes Device group messaging.

jonasgoderis commented 3 years ago

@DamienDoumer thank you for your advice regarding how to send messages to several users, will definitely take a look into that! Much appreciated!

Just wondering then in which case(s) the method SendMulticastAsync should be used then?

DamienDoumer commented 3 years ago

SendMulticastAsync is still used to send multiple messages, but topic messaging is the recommended way. I don't have any reference right now, but if I get I'll add it here.

chong-shao commented 3 years ago

@DamienDoumer is correct. topic messaging is the recommended way.

hiranya911 commented 3 years ago

Just catching up to this issue. Here are my thoughts on this:

  1. We generally discourage developers from using end user credentials (EUC) to authorize the Admin SDK. There are a number of APIs that don't work with EUC, and it seems the FCM batch API is one of them. We recommend developers to use service accounts with the GOOGLE_APPLICATION_CREDENTIALS environment variable to get app default credentials working locally (see https://firebase.google.com/docs/admin/setup#initialize-sdk).
  2. It is a little strange that the regular FCM API accepts EUC+quota_project, but the batch API doesn't. @chong-shao you might want to look into that separately. But it's not a high priority.

Update: Also note that for this use case to work as intended, you might have to set the quota project on the GoogleCredential object it self. See https://github.com/googleapis/google-api-dotnet-client/blob/master/Src/Support/Google.Apis.Auth/OAuth2/GoogleCredential.cs#L296-L307