I want to send a message to FCM topics from within my Android app. Sending the message through the Firebase console is working well, but once a user executes a particular action, I want a message to be sent to all other users who have subscribed to a particular topic.
In the documentation there is this code:
// The topic name can be optionally prefixed with "/topics/".
String topic = "highScores";
// See documentation on defining a message payload.
Message message = Message.builder()
.putData("score", "850")
.putData("time", "2:45")
.setTopic(topic)
.build();
// Send a message to the devices subscribed to the provided topic.
String response = FirebaseMessaging.getInstance().send(message);
// Response is a message ID string.
System.out.println("Successfully sent message: " + response);
I can't figure out from which class Message is. It is obviously not RemoteMessage.
You can do it by Volley and FCM API
here an example to send notification from user to "newOrder" Topic and have title and body
RequestQueue mRequestQue = Volley.newRequestQueue(this);
JSONObject json = new JSONObject();
try {
json.put("to", "/topics/" + "newOrder");
JSONObject notificationObj = new JSONObject();
notificationObj.put("title", "new Order");
notificationObj.put("body", "New order from : " + phoneNum.replace("+", " "));
//replace notification with data when went send data
json.put("notification", notificationObj);
String URL = "https://fcm.googleapis.com/fcm/send";
JsonObjectRequest request = new JsonObjectRequest(Request.Method.POST, URL,
json,
response -> Log.d("MUR", "onResponse: "),
error -> Log.d("MUR", "onError: " + error.networkResponse)
) {
#Override
public Map<String, String> getHeaders() {
Map<String, String> header = new HashMap<>();
header.put("content-type", "application/json");
header.put("authorization", "key=yourKey");
return header;
}
};
mRequestQue.add(request);
} catch (JSONException e) {
e.printStackTrace();
}
Replace yourKey with server key in your project in firebase
UPDATE :
as #frank say in correct answer You will always need a server (or otherwise trusted environment) to hold yourKey and make it not public
so this answer is already work and can send notifications from android to topic or token
but if anyone take your key can send also to your apps notifications in any time
so i suggest to use firebase functions or any service on your server just make sure your key in trusted environment and not reachable
also when get key there two type :
1 - Server key
2 - Legacy server key
like firebase say below also i suggested to use first because is more flexible to change or deleted
Firebase has upgraded our server keys to a new version. You may
continue to use your Legacy server key, but it is recommended that you
upgrade to the newest version
There is no way to securely send messages directly from one Android device to another device with Firebase Cloud Messaging. You will always need a server (or otherwise trusted environment) to do that. See this docs section showing how messages are sent and my answer. here: How to send one to one message using Firebase Messaging.
The code sample you shared is using the Admin SDK for Java to send a message, which is meant to be run in a trusted environment. It can't be used in your Android app.
Refer this link.. You can do what you need via this official google documentation.
But I can show you an example that I wrote via AndroidFastNetworking library:
JSONObject dataJsonObject = new JSONObject();
dataJsonObject.put("anyDataYouWant":"value");
try {
bodyJsonObject.put("to", "/topics/topic");
bodyJsonObject.put("data", dataJsonObject);
} catch (JSONException e) {
e.printStackTrace();
}
AndroidNetworking.post("https://fcm.googleapis.com/fcm/send")
.setContentType("application/json; charset=utf-8")
.addJSONObjectBody(bodyJsonObject)
.addHeaders("Authorization", "Your Firebase Authorization Key with 'key=' prefix: ("key=AAAAjHK....") ")
.setPriority(Priority.HIGH)
.build()
.getAsJSONObject(new JSONObjectRequestListener() {
#Override
public void onResponse(JSONObject response) {
Toast.makeText(context, "Success", Toast.LENGTH_SHORT).show();
}
#Override
public void onError(ANError anError) {
Toast.makeText(context, "Error: " + anError.toString(), Toast.LENGTH_SHORT).show();
}
});
Here's an implementation using OkHttp 4.x and Kotlin:
// create the payload
val payload = JSONObject()
.put("key", "value")
// create the request body (POST request)
val mediaType = "application/json; charset=utf-8".toMediaType()
val requestBody = JSONObject()
.put("to", "/topics/my_topic")
.put("data", payload)
.toString().toRequestBody(mediaType)
// create request
val request = Request.Builder()
.url("https://fcm.googleapis.com/fcm/send")
.post(requestBody)
.addHeader("Authorization", "key=${server_key_please_replace}")
.addHeader("Content-Type", "application/json")
.build()
// execute the call
val response = OkHttpClient().newCall(request).execute()
val responseBody = response.body?.charStream()?.readLines()
val httpCode = response.code
// error handling goes here, it's an error if the http response code is not 200
// or if the responseBody contains an error message like
// [{"multicast_id":2633602252647458018,"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"InvalidRegistration"}]}]
Security Considerations
It's not recommended to use the server API key on client side since the key can be extracted by an attacker and then used to send messages on behalf of the key owner (that would be you). I would however argue that the risk is quite low under certain circumstances:
Notification messages: the ability to send notifications to all app users is only a risk if those messages can do harm. In my case it's not possible to surface notifications because the app only processes data messages.
Data messages: in my case the app accepts specific topics but only when they are sent from the same user id (I use FCM to sync data for the same user across multiple devices). Without knowing all user ids an attacker could not even send messages. The user ids are impossible to retrieve because they are stored on the device and the user's Google Drive only so unless the Google Drive service as a whole is compromised this is a theoretical risk. On top of that, the data messages are harmless in my case.
The only real risk I see is that an attacker can launch a DOS attack so that Google shuts down access to FCM for your app. If FCM is business critical for your app then that's a real threat. BUT preventing the same attack against your backend API isn't trivial. If you have an API to send messages, that API needs to be protected. Having an endpoint to send messages might protect the FCM key but won't protect against a DOS attack per se. If users are not authenticated (in many apps they aren't) then providing that protection is a non trivial task (bot manager solutions are expensive and implementing mTLS between client and server isn't simple).
To summarize: while it's not recommended to use the server API key on client side, depending on the use case, the security risk is very low. Many apps can't afford to run a backend service just for that one functionality so I would argue using the server key client side can be justified in some cases.
Related
On my android app have many users but I want to send notification only specific user who is suitable. I don't want to send all users. if a user is already logged on app notification will pop up on the screen.
Need help, Suggestion will be highly appreciatable.
When the device firebase initialized, it will return a device token, which we normally send it the backend so it will be stored in the backend database together with the user id, then later can used for your purpose.
When a user first runs your app Firebase generates a Token for that user and his device. Each time user deletes the app and reinstalls it will re-generate this Token. This also includes new devices of course since that is just like the first installation ever.
What I did with this is that I used the Firebase Realtime database in which I stored this Token for each user I have. Then when I need to send the notification to that user I just get that token and send it within my application. This is one old example on how I did that:
OkHttpClient client = new OkHttpClient();
Logger.getLogger(OkHttpClient.class.getName()).setLevel(Level.FINE);
JSONObject json = new JSONObject();
JSONObject notifJson = new JSONObject();
JSONObject dataJson = new JSONObject();
notifJson.put("text", body);
notifJson.put("title", title);
dataJson.put("customId", "02");
json.put("notification", notifJson);
json.put("data", dataJson);
json.put("to", firebase_token);
RequestBody body = RequestBody.create(MediaType.parse("application/json"), json.toString());
.header("Authorization", "key="+server_key)
.url("https://fcm.googleapis.com/fcm/send")
.post(body)
.build();
Response response = client.newCall(request).execute();
if (response.isSuccessful()) { //do something if notification is sent successfully }
You can do this using your code to send notifications to a specific user. This is, of course, if you want to send a notification from your code. If you want to use a server or something there is documentation you can follow here: https://firebase.google.com/docs/cloud-messaging/android/send-multiple
What I used in this example:
notifJson - notification part
dataJson - data part of notification
body - the body of my notification
title - the title of my notification
firebase_token - Token of user I am sending a notification to, this is retrieved earlier with Firebase real-time database
server_key - Server key from Firebase configuration
In the IF statement below if the response was successful I add some data to my real-time database but you can do anything else you want.
I execute this code to push notifications to mobile device using FCM library
public string PushFCMNotification(string deviceId, string message)
{
string SERVER_API_KEY = "xxxxxxxxxxxxxxxxxxxxxxx";
var SENDER_ID = "xxxxxxxxx";
var value = message;
WebRequest tRequest;
tRequest = WebRequest.Create("https://fcm.googleapis.com/fcm/send");
tRequest.Method = "post";
tRequest.ContentType = "application/json";
tRequest.Headers.Add(string.Format("Authorization: key={0}", SERVER_API_KEY));
tRequest.Headers.Add(string.Format("Sender: id={0}", SENDER_ID));
var data = new
{
to = deviceId,
notification = new
{
body = "This is the message",
title = "This is the title",
icon = "myicon"
}
};
var serializer = new JavaScriptSerializer();
var json = serializer.Serialize(data);
Byte[] byteArray = Encoding.UTF8.GetBytes(json);
tRequest.ContentLength = byteArray.Length;
Stream dataStream = tRequest.GetRequestStream();
dataStream.Write(byteArray, 0, byteArray.Length);
dataStream.Close();
WebResponse tResponse = tRequest.GetResponse();
dataStream = tResponse.GetResponseStream();
StreamReader tReader = new StreamReader(dataStream);
String sResponseFromServer = tReader.ReadToEnd();
tReader.Close();
dataStream.Close();
tResponse.Close();
return sResponseFromServer;
}
now, how to send message to multi device,
assume that string deviceId parameter replaced with List devicesIDs.
can you help
Update: For v1, it seems that registration_ids is no longer supported. It is strongly suggested that topics be used instead. Only the parameters shown in the documentation are supported for v1.
Simply use the registration_ids parameter instead of to in your payload. Depending also on your use case, you may use either Topic Messaging or Device Group Messaging.
Topic Messaging
Firebase Cloud Messaging (FCM) topic messaging allows you to send a message to multiple devices that have opted in to a particular topic. Based on the publish/subscribe model, topic messaging supports unlimited subscriptions for each app. You compose topic messages as needed, and Firebase handles message routing and delivering the message reliably to the right devices.
For example, users of a local weather forecasting app could opt in to a "severe weather alerts" topic and receive notifications of storms threatening specified areas. Users of a sports app could subscribe to automatic updates in live game scores for their favorite teams. Developers can choose any topic name that matches the regular expression: "/topics/[a-zA-Z0-9-_.~%]+".
Device Group Messaging
With device group messaging, app servers can send a single message to multiple instances of an app running on devices belonging to a group. Typically, "group" refers a set of different devices that belong to a single user. All devices in a group share a common notification key, which is the token that FCM uses to fan out messages to all devices in the group.
Device group messaging makes it possible for every app instance in a group to reflect the latest messaging state. In addition to sending messages downstream to a notification key, you can enable devices to send upstream messages to a device group. You can use device group messaging with either the XMPP or HTTP connection server. The limit on data payload is 2KB when sending to iOS devices, and 4KB for other platforms.
The maximum number of members allowed for a notification_key is 20.
For more details, you can check out the Sending to Multiple Devices in FCM docs.
You should create a Topic and let users subscribe to that topic.
That way, when you send an FCM message, every user subscribed gets it, except you actually want to keep record of their Id's for special purposes.
FirebaseMessaging.getInstance().subscribeToTopic("news");
See this link: https://firebase.google.com/docs/cloud-messaging/android/topic-messaging
https://fcm.googleapis.com/fcm/send
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA
{
"to": "/topics/news",
"data": {
"message": "This is a Firebase Cloud Messaging Topic Message!",
}
}
Please follow these steps.
public String addNotificationKey(
String senderId, String userEmail, String registrationId, String idToken)
throws IOException, JSONException {
URL url = new URL("https://android.googleapis.com/gcm/googlenotification");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setDoOutput(true);
// HTTP request header
con.setRequestProperty("project_id", senderId);
con.setRequestProperty("Content-Type", "application/json");
con.setRequestProperty("Accept", "application/json");
con.setRequestMethod("POST");
con.connect();
// HTTP request
JSONObject data = new JSONObject();
data.put("operation", "add");
data.put("notification_key_name", userEmail);
data.put("registration_ids", new JSONArray(Arrays.asList(registrationId)));
data.put("id_token", idToken);
OutputStream os = con.getOutputStream();
os.write(data.toString().getBytes("UTF-8"));
os.close();
// Read the response into a string
InputStream is = con.getInputStream();
String responseString = new Scanner(is, "UTF-8").useDelimiter("\\A").next();
is.close();
// Parse the JSON string and return the notification key
JSONObject response = new JSONObject(responseString);
return response.getString("notification_key");
}
I hope the above code will help you to send push on multiple devices.
For more detail please refer this link https://firebase.google.com/docs/cloud-messaging/android/device-group
***Note : Please must read the about creating/removing group by the above link.
A word of caution mentioned in FCM DOcument which is as follows,
Caution: Any apps that use device group messaging must continue to use the legacy API for the management of device groups (creating, updating, etc.). The HTTP v1 can send messages to device groups, but does not support management.
https://firebase.google.com/docs/cloud-messaging/migrate-v1
Also the Admin SDK's uses a Batch HttpPostrequest to make it easy for consumers, so if you want Device Group messaging you could still uses the New V1 FCM API, but using FCM Admin SDK.
Here is the code from Admin SDK which does this job for you.
Class Name: FirebaseMessagingClientImpl
for (Message message : messages) {
// Using a separate request factory without authorization is faster for large batches.
// A simple performance test showed a 400-500ms speed up for batches of 1000 messages.
HttpRequest request = childRequestFactory.buildPostRequest(
sendUrl,
new JsonHttpContent(jsonFactory, message.wrapForTransport(dryRun)));
request.setParser(jsonParser);
setCommonFcmHeaders(request.getHeaders());
batch.queue(
request, MessagingServiceResponse.class, MessagingServiceErrorResponse.class, callback);
}
I execute this code to push notifications to mobile device using FCM library
public string PushFCMNotification(string deviceId, string message)
{
string SERVER_API_KEY = "xxxxxxxxxxxxxxxxxxxxxxx";
var SENDER_ID = "xxxxxxxxx";
var value = message;
WebRequest tRequest;
tRequest = WebRequest.Create("https://fcm.googleapis.com/fcm/send");
tRequest.Method = "post";
tRequest.ContentType = "application/json";
tRequest.Headers.Add(string.Format("Authorization: key={0}", SERVER_API_KEY));
tRequest.Headers.Add(string.Format("Sender: id={0}", SENDER_ID));
var data = new
{
to = deviceId,
notification = new
{
body = "This is the message",
title = "This is the title",
icon = "myicon"
}
};
var serializer = new JavaScriptSerializer();
var json = serializer.Serialize(data);
Byte[] byteArray = Encoding.UTF8.GetBytes(json);
tRequest.ContentLength = byteArray.Length;
Stream dataStream = tRequest.GetRequestStream();
dataStream.Write(byteArray, 0, byteArray.Length);
dataStream.Close();
WebResponse tResponse = tRequest.GetResponse();
dataStream = tResponse.GetResponseStream();
StreamReader tReader = new StreamReader(dataStream);
String sResponseFromServer = tReader.ReadToEnd();
tReader.Close();
dataStream.Close();
tResponse.Close();
return sResponseFromServer;
}
now, how to send message to multi device,
assume that string deviceId parameter replaced with List devicesIDs.
can you help
Update: For v1, it seems that registration_ids is no longer supported. It is strongly suggested that topics be used instead. Only the parameters shown in the documentation are supported for v1.
Simply use the registration_ids parameter instead of to in your payload. Depending also on your use case, you may use either Topic Messaging or Device Group Messaging.
Topic Messaging
Firebase Cloud Messaging (FCM) topic messaging allows you to send a message to multiple devices that have opted in to a particular topic. Based on the publish/subscribe model, topic messaging supports unlimited subscriptions for each app. You compose topic messages as needed, and Firebase handles message routing and delivering the message reliably to the right devices.
For example, users of a local weather forecasting app could opt in to a "severe weather alerts" topic and receive notifications of storms threatening specified areas. Users of a sports app could subscribe to automatic updates in live game scores for their favorite teams. Developers can choose any topic name that matches the regular expression: "/topics/[a-zA-Z0-9-_.~%]+".
Device Group Messaging
With device group messaging, app servers can send a single message to multiple instances of an app running on devices belonging to a group. Typically, "group" refers a set of different devices that belong to a single user. All devices in a group share a common notification key, which is the token that FCM uses to fan out messages to all devices in the group.
Device group messaging makes it possible for every app instance in a group to reflect the latest messaging state. In addition to sending messages downstream to a notification key, you can enable devices to send upstream messages to a device group. You can use device group messaging with either the XMPP or HTTP connection server. The limit on data payload is 2KB when sending to iOS devices, and 4KB for other platforms.
The maximum number of members allowed for a notification_key is 20.
For more details, you can check out the Sending to Multiple Devices in FCM docs.
You should create a Topic and let users subscribe to that topic.
That way, when you send an FCM message, every user subscribed gets it, except you actually want to keep record of their Id's for special purposes.
FirebaseMessaging.getInstance().subscribeToTopic("news");
See this link: https://firebase.google.com/docs/cloud-messaging/android/topic-messaging
https://fcm.googleapis.com/fcm/send
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA
{
"to": "/topics/news",
"data": {
"message": "This is a Firebase Cloud Messaging Topic Message!",
}
}
Please follow these steps.
public String addNotificationKey(
String senderId, String userEmail, String registrationId, String idToken)
throws IOException, JSONException {
URL url = new URL("https://android.googleapis.com/gcm/googlenotification");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setDoOutput(true);
// HTTP request header
con.setRequestProperty("project_id", senderId);
con.setRequestProperty("Content-Type", "application/json");
con.setRequestProperty("Accept", "application/json");
con.setRequestMethod("POST");
con.connect();
// HTTP request
JSONObject data = new JSONObject();
data.put("operation", "add");
data.put("notification_key_name", userEmail);
data.put("registration_ids", new JSONArray(Arrays.asList(registrationId)));
data.put("id_token", idToken);
OutputStream os = con.getOutputStream();
os.write(data.toString().getBytes("UTF-8"));
os.close();
// Read the response into a string
InputStream is = con.getInputStream();
String responseString = new Scanner(is, "UTF-8").useDelimiter("\\A").next();
is.close();
// Parse the JSON string and return the notification key
JSONObject response = new JSONObject(responseString);
return response.getString("notification_key");
}
I hope the above code will help you to send push on multiple devices.
For more detail please refer this link https://firebase.google.com/docs/cloud-messaging/android/device-group
***Note : Please must read the about creating/removing group by the above link.
A word of caution mentioned in FCM DOcument which is as follows,
Caution: Any apps that use device group messaging must continue to use the legacy API for the management of device groups (creating, updating, etc.). The HTTP v1 can send messages to device groups, but does not support management.
https://firebase.google.com/docs/cloud-messaging/migrate-v1
Also the Admin SDK's uses a Batch HttpPostrequest to make it easy for consumers, so if you want Device Group messaging you could still uses the New V1 FCM API, but using FCM Admin SDK.
Here is the code from Admin SDK which does this job for you.
Class Name: FirebaseMessagingClientImpl
for (Message message : messages) {
// Using a separate request factory without authorization is faster for large batches.
// A simple performance test showed a 400-500ms speed up for batches of 1000 messages.
HttpRequest request = childRequestFactory.buildPostRequest(
sendUrl,
new JsonHttpContent(jsonFactory, message.wrapForTransport(dryRun)));
request.setParser(jsonParser);
setCommonFcmHeaders(request.getHeaders());
batch.queue(
request, MessagingServiceResponse.class, MessagingServiceErrorResponse.class, callback);
}
Hi would like to send an sms verification to users phone numbers.I have done it in ios by calling the rest Api and it works.
But can anyone help me with android I get an error :
code=401, message=UNAUTHORIZED.
Any help would be apreciated?
url = https://{AccountSid}:{AuthToken}#api.twilio.com/2010-04-01/AccountsAccountSid}/SMS/Messages
private OkHttpClient mClient2 = new OkHttpClient();
Call post(String url, Callback callback) throws IOException {
Random rand = new Random();
randomNum = 1000 + rand.nextInt((100000 - 1000) + 1);
RequestBody formBody = new FormBody.Builder()
.add("To", etNumber.getText().toString())
.add("From", "+mynum")
.add("Body", "Your confirmation code for United taxi is" + randomNum)
.build();
Request request = new Request.Builder()
.url(url)
.post(formBody)
.build();
Call response = mClient2.newCall(request);
System.out.println(request.url());
response.enqueue(callback);
return response;
}
The WWW-Authenticate header is:
WWW-Authenticate: Basic realm="Twilio API"
Twilio Developer Evangelist here.
I actually happen to have written a blog post explaining why you shouldn't make requests like this directly from the device, but have a server in the middle. It will not only make your account more secure, but make it much easier to debug.
Best of all, even if you don't wanna build a backend yourself, my blog post shows you how to deploy one with the click of a button.
Check it out here or get started with your server-side language of choice and finish up with the post.
As for the error you're getting, UNAUTHORIZED implies you're not passing the correct AccountSid & AuthToken. If you still want to go down the route of making the request directly from the device, which again, we don't recommend, I would suggest trying to make the request from something like Postman for example, to make sure you've got your credentials right, and then moving that to the device using OkHttp.
I need a little help understanding couple of things about gcm - server side.
I am working on instant messaging app with the example of server-side here http://developer.android.com/google/gcm/ccs.html
After I execute this code It connects but terminates after few seconds. I can guess that the reason it's because of the main() function.
public static void main(String[] args) throws Exception {
final long senderId = 1234567890L; // your GCM sender id
final String password = "Your API key";
SmackCcsClient ccsClient = new SmackCcsClient();
ccsClient.connect(senderId, password);
// Send a sample hello downstream message to a device.
String toRegId = "RegistrationIdOfTheTargetDevice";
String messageId = ccsClient.nextMessageId();
Map<String, String> payload = new HashMap<String, String>();
payload.put("Hello", "World");
payload.put("CCS", "Dummy Message");
payload.put("EmbeddedMessageId", messageId);
String collapseKey = "sample";
Long timeToLive = 10000L;
String message = createJsonMessage(toRegId, messageId, payload,
collapseKey, timeToLive, true);
ccsClient.sendDownstreamMessage(message);
}
Isn't supposed to be a 'while' statement which keeps the XMPP connection alive between the app-server to GCM server?
Isn't The purpose of the server-side in this case (instant messaging app) is to keep the connection alive with GCM server and to listen for incoming messages from clients.
messages like:
2.1 When client want to register to the app so I need to store it's details in a database.
2.2 When client want to send message to another client so the server-side app is in charge to forward the message to it's destination?
I have looked for couple of examples for implementing gcm server side using xmpp and all of them were with this kind of main function I mentioned above... Am I getting something wrong?
Yes, the XMPP connection must stay alive. Otherwise, your server won't be able to receive upstream (device to cloud) messages.
I can't say why all the examples you saw don't maintain an open connection. I guess they are simplified examples.