Firebase Cloud Messaging notification from Java instead of Firebase Console

Sanjay Saxena picture Sanjay Saxena · May 25, 2016 · Viewed 47.2k times · Source

I am new to Firebase and I am running into errors such as mismatch sender id and Authentication Error with HTTP status 401 when my Java application tries to send a notification/message using the client's Firebase token. Note that using Firebase Console, I am able to send the message to the client.

For the client app, I did the following:

  1. Cloned Firebase messaging quickstart app from here -- https://github.com/firebase/quickstart-android/tree/master/messaging -- which is already setup with Firebase dependencies and such. Loaded the cloned app in Android Studio.
  2. Using Firebase Console, created a new project called My Notification Client and added an app using com.google.firebase.quickstart.fcm as the package name of the Android app.
  3. Downloaded google-services.json file for the Android app added to the newly created project and copied it to the messaging/app/ folder and sync'd with Grade files from within Android Studio.
  4. Created an emulator and ran the app from within Android Studio. When the app got loaded in the emulator, clicked the Log Token button to capture the client's Firebase token in the Android Monitor tab in Android Studio.
  5. Using Firebase Console, selected the newly created project My Notification Client, clicked the Notifications link from the left pane, and sent a notification to the device by pasting the Firebase token captured in the previous step.

This worked great! The next step was to to use a separate simple Java application (which would eventually become a notification server) to send the notification to the client instead of using Firebase Console.

To accomplish this, I followed the steps outlined here -- https://firebase.google.com/docs/server/setup#prerequisites. Specifically, these are the steps that I performed:

  1. Created a new project called My Notification Server in Firebase Console.
  2. Using Settings > Permissions in Firebase Console, created a new service account and downloaded the serviceAccountCredentials.json file.
  3. Created a new maven project in Eclipse and added the following dependencies in pom.xml:
    <dependency>
      <groupId>com.google.gcm</groupId>
      <artifactId>gcm-server</artifactId>
      <version>1.0.0</version>
    </dependency>
    <dependency>
      <groupId>com.googlecode.json-simple</groupId>
      <artifactId>json-simple</artifactId>
      <version>1.1.1</version>
    </dependency>
    <dependency>
      <groupId>com.google.firebase</groupId>
      <artifactId>firebase-server-sdk</artifactId>
      <version>3.0.0</version>
    </dependency>
  1. Then, I extended com.google.android.gcm.sender.Sender to create FCMSender and override protected method getConnection(String url) to use the new FCM endpoint as shown below:

    class FCMSender extends Sender {
    
        public FCMSender(String key) {
            super(key);
        }
    
        @Override
        protected HttpURLConnection getConnection(String url) throws IOException {
            String fcmUrl = "https://fcm.googleapis.com/fcm/send";
            return (HttpURLConnection) new URL(fcmUrl).openConnection();
        }
    }
    
  2. The main() method of my Java application looks like this:

    public static void main(String[] args) {
        // Obtain serverKey from Project Settings -> Cloud Messaging tab
        // for "My Notification Client" project in Firebase Console.
        String serverKey = <get_server_key_from_firebase_console>;
        Thread t = new Thread() {
            public void run(){ 
                try {
                    Sender sender = new FCMSender(serverKey);
                    Message message = new Message.Builder()
                                      .collapseKey("message")
                                      .timeToLive(3)
                                      .delayWhileIdle(true)
                                      .addData("message", "Notification from Java application");
                                      .build();  
    
                    // Use the same token(or registration id) that was earlier
                    // used to send the message to the client directly from
                    // Firebase Console's Notification tab.
                    Result result = sender.send(message,
                "APA91bFfIFjSCcSiJ111rbmkpnMkZY-Ej4RCpdBZFZN_mYgfHwFlx-M1UXS5FqDBcN8x1efrS2md8L9K_E9N21qB-PIHUqQwmF4p7Y3U-86nCGH7KNkZNjjz_P_qjcTR0TOrwXMh33vp",
                        1);
                    System.out.println("Result: " + result.toString());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            };
            t.start;
            try {
                t.join();
            }
            catch (InterruptedException iex) {
                iex.printStackTrace();
            }
        }
    

When I run the Java application, I get MismatchSenderId error. I tried troubleshooting using curl as shown below but got the same error:

$ skey=<obtained_from_project_settings_cloud_messaging_tab_in_firebase_console>
$ curl -X POST --header "Authorization: key=$skey" \
  --Header "Content-Type: application/json" \
  https://fcm.googleapis.com/fcm/send \
  -d "{\"to\":\"APA91bFfIFjSCcSiJ111rbmkpnMkZY-Ej4RCpdBZFZN_mYgfHwFlx-M1UXS5FqDBcN8x1efrS2md8L9K_E9N21qB-PIHUqQwmF4p7Y3U-86nCGH7KNkZNjjz_P_qjcTR0TOrwXMh33vp\",\"data\":{\"message\":\"Yellow\"}}"

{"multicast_id":7391851081942579857,"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"MismatchSenderId"}]}

Instead of using the Server Key listed on Project Settings > Cloud Messaging tab for My Notification Server project in Firebase Console, I tried using the Project Number but that caused InvalidRequestException: HTTP Status Code: 401.

Firebase documentation of the error codes says this about MismatchSenderId:

Mismatched Sender 200 + error:MismatchSenderId
A registration token is tied to a certain group of senders. When a client app registers for FCM, it must specify which senders are allowed to send messages. You should use one of those sender IDs when sending messages to the client app. If you switch to a different sender, the existing registration tokens won't work.

I could not figure out where/how in Firebase Console one can configure the Android app(which was aded to the My Notification Client project) to be able to receive messages.

Any help on this, would be appreciated!

Answer

aes picture aes · Jun 1, 2016

I too have recently started using Firebase, with no prior experience in Google Cloud Messaging. AFAIK, there's no server side SDK for Firebase Cloud Messaging, currently.

To send notifications/messages from your App Server to your Mobile Client, you need to implement your own HTTP and/or XMPP methods. See https://firebase.google.com/docs/cloud-messaging/

My testing code, using HTTP:

public CompletableFuture<String> fcmTest1() {
    // https://firebase.google.com/docs/cloud-messaging/http-server-ref
    String host = "fcm.googleapis.com";
    String requestURI = "/fcm/send";
    String CLIENT_TOKEN = "ASK_YOUR_MOBILE_CLIENT_DEV"; // https://developers.google.com/instance-id/

    CompletableFuture<String> fut = new CompletableFuture<>();
    JsonObject body = new JsonObject();
    // JsonArray registration_ids = new JsonArray();
    // body.put("registration_ids", registration_ids);
    body.put("to", CLIENT_TOKEN);
    body.put("priority", "high");
    // body.put("dry_run", true);

    JsonObject notification = new JsonObject();
    notification.put("body", "body string here");
    notification.put("title", "title string here");
    // notification.put("icon", "myicon");

    JsonObject data = new JsonObject();
    data.put("key1", "value1");
    data.put("key2", "value2");

    body.put("notification", notification);
    body.put("data", data);

    HttpClientRequest hcr = httpsClient.post(443, host, requestURI).handler(response -> {
        response.bodyHandler(buffer -> {
            logger.debug("FcmTest1 rcvd: {}, {}", response.statusCode(), buffer.toString());
            if (response.statusCode() == 200) {
                fut.complete(buffer.toString());
            } else {
                fut.completeExceptionally(new RuntimeException(buffer.toString()));
            }
        });
    });
    hcr.putHeader("Authorization", "key=" + Utils.FIREBASE_SERVER_KEY)
        .putHeader("content-type", "application/json").end(body.encode());
    return fut;
}

note: http client and json were provided by vertx (http://vertx.io/), but hopefully the code shows what's going on clear enough so you can use whatever you wish.