Related
I have been trying to read the official docs and guides about how to send message from one device to another. I have saved registration token of both devices in the Real Time Database, thus I have the registration token of another device.
I have tried the following way to send the message
RemoteMessage message = new RemoteMessage.Builder(getRegistrationToken())
.setMessageId(incrementIdAndGet())
.addData("message", "Hello")
.build();
FirebaseMessaging.getInstance().send(message);
However this is not working. The other device doesn't receive any message. I am not even sure, if I can use upstream message sending to conduct device to device communication.
PS: I just want to know if device-to-device messaging is possible using FCM? If yes, then is the code I used have some issue? If yes, then what is the correct way.
Update:
My question was to ask whether device to device messaging without using any separate server other than firebase could messaging is possible or not, if yes than how, since there's no documentation about it. I do not understand what is left to explain here? Anyways I got the answer and will update it as an answer once the question gets reopened.
Firebase has two features to send messages to devices:
the Notifications panel in your Firebase Console allows you to send notifications to specific devices, groups of users, or topics that users subscribed to.
by calling Firebase Cloud Messaging API, you can send messages with whatever targeting strategy you prefer. Calling the FCM API requires access to your Server key, which you should never expose on client devices. That's why you should always run such code on an app server.
The Firebase documentation shows this visually:
Sending messages from one device directly to another device is not supported through the Firebase Cloud Messaging client-side SDKs.
Update: I wrote a blog post detailing how to send notifications between Android devices using Firebase Database, Cloud Messaging and Node.js.
Update 2: You can now also use Cloud Functions for Firebase to send messages securely, without spinning up a server. See this sample use-case to get started. If you don't want to use Cloud Functions, you can run the same logic on any trusted environment you already have, such as your development machine, or a server you control.
Warning There is a very important reason why we don't mention this approach anywhere. This exposes your server key in the APK that
you put on every client device. It can (and thus will) be taken from
there and may lead to abuse of your project. I highly recommend
against taking this approach, except for apps that you only put on
your own devices. – Frank van Puffelen
Ok, so the answer by Frank was correct that Firebase does not natively support device to device messaging. However there's one loophole in that. The Firebase server doesn't identify whether you have send the request from an actual server or are you doing it from your device.
So all you have to do is send a Post Request to Firebase's messaging server along with the Server Key. Just keep this in mind that the server key is not supposed to be on the device, but there's no other option if you want device-to-device messaging using Firebase Messaging.
I am using OkHTTP instead of default way of calling the Rest API. The code is something like this -
public static final String FCM_MESSAGE_URL = "https://fcm.googleapis.com/fcm/send";
OkHttpClient mClient = new OkHttpClient();
public void sendMessage(final JSONArray recipients, final String title, final String body, final String icon, final String message) {
new AsyncTask<String, String, String>() {
#Override
protected String doInBackground(String... params) {
try {
JSONObject root = new JSONObject();
JSONObject notification = new JSONObject();
notification.put("body", body);
notification.put("title", title);
notification.put("icon", icon);
JSONObject data = new JSONObject();
data.put("message", message);
root.put("notification", notification);
root.put("data", data);
root.put("registration_ids", recipients);
String result = postToFCM(root.toString());
Log.d(TAG, "Result: " + result);
return result;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(String result) {
try {
JSONObject resultJson = new JSONObject(result);
int success, failure;
success = resultJson.getInt("success");
failure = resultJson.getInt("failure");
Toast.makeText(getCurrentActivity(), "Message Success: " + success + "Message Failed: " + failure, Toast.LENGTH_LONG).show();
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(getCurrentActivity(), "Message Failed, Unknown error occurred.", Toast.LENGTH_LONG).show();
}
}
}.execute();
}
String postToFCM(String bodyString) throws IOException {
RequestBody body = RequestBody.create(JSON, bodyString);
Request request = new Request.Builder()
.url(FCM_MESSAGE_URL)
.post(body)
.addHeader("Authorization", "key=" + SERVER_KEY)
.build();
Response response = mClient.newCall(request).execute();
return response.body().string();
}
I hope Firebase will come with a better solution in future. But till then, I think this is the only way. The other way would be to send topic message or group messaging. But that was not in the scope of the question.
Update:
The JSONArray is defined like this -
JSONArray regArray = new JSONArray(regIds);
regIds is a String array of registration ids, you want to send this message to. Keep in mind that the registration ids must always be in an array, even if you want it to send to a single recipient.
I have also been using direct device to device gcm messaging in my prototype. It has been working very well. We dont have any server. We exchange GCM reg id using sms/text and then communicate using GCM after that. I am putting here code related to GCM handling
**************Sending GCM Message*************
//Sends gcm message Asynchronously
public class GCM_Sender extends IntentService{
final String API_KEY = "****************************************";
//Empty constructor
public GCM_Sender() {
super("GCM_Sender");
}
//Processes gcm send messages
#Override
protected void onHandleIntent(Intent intent) {
Log.d("Action Service", "GCM_Sender Service Started");
//Get message from intent
String msg = intent.getStringExtra("msg");
msg = "\"" + msg + "\"";
try{
String ControllerRegistrationId = null;
//Check registration id in db
if(RegistrationIdAdapter.getInstance(getApplicationContext()).getRegIds().size() > 0 ) {
String controllerRegIdArray[] = RegistrationIdAdapter.getInstance(getApplicationContext()).getRegIds().get(1);
if(controllerRegIdArray.length>0)
ControllerRegistrationId = controllerRegIdArray[controllerRegIdArray.length-1];
if(!ControllerRegistrationId.equalsIgnoreCase("NULL")){
// 1. URL
URL url = new URL("https://android.googleapis.com/gcm/send");
// 2. Open connection
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
// 3. Specify POST method
urlConnection.setRequestMethod("POST");
// 4. Set the headers
urlConnection.setRequestProperty("Content-Type", "application/json");
urlConnection.setRequestProperty("Authorization", "key=" + API_KEY);
urlConnection.setDoOutput(true);
// 5. Add JSON data into POST request body
JSONObject obj = new JSONObject("{\"time_to_live\": 0,\"delay_while_idle\": true,\"data\":{\"message\":" + msg + "},\"registration_ids\":[" + ControllerRegistrationId + "]}");
// 6. Get connection output stream
OutputStreamWriter out = new OutputStreamWriter(urlConnection.getOutputStream());
out.write(obj.toString());
out.close();
// 6. Get the response
int responseCode = urlConnection.getResponseCode();
BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null){
response.append(inputLine);
}
in.close();
Log.d("GCM getResponseCode:", new Integer(responseCode).toString());
}else{
Log.d("GCM_Sender:","Field REGISTRATION_TABLE is null");
}
}else {
Log.d("GCM_Sender:","There is no Registration ID in DB ,please sync devices");
}
} catch (Exception e) {
e.printStackTrace();
//MessageSender.getInstance().sendMessage(msg, Commands.SMS_MESSAGE);
}
}
//Called when service is no longer alive
#Override
public void onDestroy() {
super.onDestroy();
//Do a log that GCM_Sender service has been destroyed
Log.d("Action Service", "GCM_Sender Service Destroyed");
}
}
**************Receiving GCM Message*************
public class GCM_Receiver extends WakefulBroadcastReceiver {
public static final String RETRY_ACTION ="com.google.android.c2dm.intent.RETRY";
public static final String REGISTRATION ="com.google.android.c2dm.intent.REGISTRATION";
public SharedPreferences preferences;
//Processes Gcm message .
#Override
public void onReceive(Context context, Intent intent) {
ComponentName comp = new ComponentName(context.getPackageName(),
GCMNotificationIntentService.class.getName());
//Start GCMNotificationIntentService to handle gcm message asynchronously
startWakefulService(context, (intent.setComponent(comp)));
setResultCode(Activity.RESULT_OK);
/*//Check if DatabaseService is running .
if(!DatabaseService.isServiceRunning) {
Intent dbService = new Intent(context,DatabaseService.class);
context.startService(dbService);
}*/
//Check if action is RETRY_ACTION ,if it is then do gcm registration again .
if(intent.getAction().equals(RETRY_ACTION)) {
String registrationId = intent.getStringExtra("registration_id");
if(TextUtils.isEmpty(registrationId)){
DeviceRegistrar.getInstance().register(context);
}else {
//Save registration id to prefs .
preferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("BLACKBOX_REG_ID",registrationId);
editor.commit();
}
} else if (intent.getAction().equals(REGISTRATION)) {
}
}
}
//Processes gcm messages asynchronously .
public class GCMNotificationIntentService extends IntentService{
public static final int NOTIFICATION_ID = 1;
private NotificationManager mNotificationManager;
String gcmData;
private final String TAG = "GCMNotificationIntentService";
//Constructor with super().
public GCMNotificationIntentService() {
super("GcmIntentService");
}
//Called when startService() is called by its Client .
//Processes gcm messages .
#Override
protected void onHandleIntent(Intent intent) {
Log.d("GCMNotificationIntentService", "GCMNotificationIntentService Started");
Bundle extras = intent.getExtras();
//Get instance of GoogleCloudMessaging .
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
//Get gcm message type .
String messageType = gcm.getMessageType(intent);
if (!extras.isEmpty()) {
if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR
.equals(messageType)) {
sendNotification("Send error: " + extras.toString());
} else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED
.equals(messageType)) {
sendNotification("Deleted messages on server: "
+ extras.toString());
} else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE
.equals(messageType)) {
Log.i(TAG, "Completed work # " + SystemClock.elapsedRealtime());
gcmData = extras.getString("message");
Intent actionService = new Intent(getApplicationContext(),Action.class);
actionService.putExtra("data", gcmData);
//start Action service .
startService(actionService);
//Show push notification .
sendNotification("Action: " + gcmData);
//Process received gcmData.
Log.d(TAG,"Received Gcm Message from Controller : " + extras.getString("message"));
}
}
GCM_Receiver.completeWakefulIntent(intent);
}
//Shows notification on device notification bar .
private void sendNotification(String msg) {
mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
Intent notificationIntent = new Intent(this, BlackboxStarter.class);
//Clicking on GCM notification add new layer of app.
notificationIntent.setFlags( Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
this).setSmallIcon(R.drawable.gcm_cloud)
.setContentTitle("Notification from Controller")
.setStyle(new NotificationCompat.BigTextStyle().bigText(msg))
.setContentText(msg);
mBuilder.setContentIntent(contentIntent);
mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
//Play default notification
try {
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
Ringtone r = RingtoneManager.getRingtone(getApplicationContext(), notification);
r.play();
} catch (Exception e) {
e.printStackTrace();
}
}
//Called when service is no longer be available .
#Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
Log.d("GCMNotificationIntentService", "GCMNotificationIntentService Destroyed");
}
}
According to the new documentation which was updated on October 2, 2018 you must send post request as below
https://fcm.googleapis.com/fcm/send
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA //Server key
{
"to": "sent device's registration token",
"data": {
"hello": "message from someone",
}
}
To get device's registration token extend FirebaseMessagingService and override onNewToken(String token)
For more info refer to doc https://firebase.google.com/docs/cloud-messaging/android/device-group
I am late but above solutions has helped me to write down this simple answer, you can send your message directly to android devices from android application, here is the simple implementation I have done and it works great for me.
compile android volley library
compile 'com.android.volley:volley:1.0.0'
Just copy paste this simple function ;) and your life will become smooth just like knife in butter. :D
public static void sendPushToSingleInstance(final Context activity, final HashMap dataValue /*your data from the activity*/, final String instanceIdToken /*firebase instance token you will find in documentation that how to get this*/ ) {
final String url = "https://fcm.googleapis.com/fcm/send";
StringRequest myReq = new StringRequest(Request.Method.POST,url,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
Toast.makeText(activity, "Bingo Success", Toast.LENGTH_SHORT).show();
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(activity, "Oops error", Toast.LENGTH_SHORT).show();
}
}) {
#Override
public byte[] getBody() throws com.android.volley.AuthFailureError {
Map<String, Object> rawParameters = new Hashtable();
rawParameters.put("data", new JSONObject(dataValue));
rawParameters.put("to", instanceIdToken);
return new JSONObject(rawParameters).toString().getBytes();
};
public String getBodyContentType()
{
return "application/json; charset=utf-8";
}
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("Authorization", "key="+YOUR_LEGACY_SERVER_KEY_FROM_FIREBASE_CONSOLE);
headers.put("Content-Type","application/json");
return headers;
}
};
Volley.newRequestQueue(activity).add(myReq);
}
Note
If you want to send message to topics so you can change parameter instanceIdToken to something like /topics/topicName.
For groups implementation is the same but you just need to take care of parameters. checkout Firebase documentation and you can pass those parameters.
let me know if you face any issue.
I have been trying to read the official docs and guides about how to send message from one device to another. I have saved registration token of both devices in the Real Time Database, thus I have the registration token of another device.
I have tried the following way to send the message
RemoteMessage message = new RemoteMessage.Builder(getRegistrationToken())
.setMessageId(incrementIdAndGet())
.addData("message", "Hello")
.build();
FirebaseMessaging.getInstance().send(message);
However this is not working. The other device doesn't receive any message. I am not even sure, if I can use upstream message sending to conduct device to device communication.
PS: I just want to know if device-to-device messaging is possible using FCM? If yes, then is the code I used have some issue? If yes, then what is the correct way.
Update:
My question was to ask whether device to device messaging without using any separate server other than firebase could messaging is possible or not, if yes than how, since there's no documentation about it. I do not understand what is left to explain here? Anyways I got the answer and will update it as an answer once the question gets reopened.
Firebase has two features to send messages to devices:
the Notifications panel in your Firebase Console allows you to send notifications to specific devices, groups of users, or topics that users subscribed to.
by calling Firebase Cloud Messaging API, you can send messages with whatever targeting strategy you prefer. Calling the FCM API requires access to your Server key, which you should never expose on client devices. That's why you should always run such code on an app server.
The Firebase documentation shows this visually:
Sending messages from one device directly to another device is not supported through the Firebase Cloud Messaging client-side SDKs.
Update: I wrote a blog post detailing how to send notifications between Android devices using Firebase Database, Cloud Messaging and Node.js.
Update 2: You can now also use Cloud Functions for Firebase to send messages securely, without spinning up a server. See this sample use-case to get started. If you don't want to use Cloud Functions, you can run the same logic on any trusted environment you already have, such as your development machine, or a server you control.
Warning There is a very important reason why we don't mention this approach anywhere. This exposes your server key in the APK that
you put on every client device. It can (and thus will) be taken from
there and may lead to abuse of your project. I highly recommend
against taking this approach, except for apps that you only put on
your own devices. – Frank van Puffelen
Ok, so the answer by Frank was correct that Firebase does not natively support device to device messaging. However there's one loophole in that. The Firebase server doesn't identify whether you have send the request from an actual server or are you doing it from your device.
So all you have to do is send a Post Request to Firebase's messaging server along with the Server Key. Just keep this in mind that the server key is not supposed to be on the device, but there's no other option if you want device-to-device messaging using Firebase Messaging.
I am using OkHTTP instead of default way of calling the Rest API. The code is something like this -
public static final String FCM_MESSAGE_URL = "https://fcm.googleapis.com/fcm/send";
OkHttpClient mClient = new OkHttpClient();
public void sendMessage(final JSONArray recipients, final String title, final String body, final String icon, final String message) {
new AsyncTask<String, String, String>() {
#Override
protected String doInBackground(String... params) {
try {
JSONObject root = new JSONObject();
JSONObject notification = new JSONObject();
notification.put("body", body);
notification.put("title", title);
notification.put("icon", icon);
JSONObject data = new JSONObject();
data.put("message", message);
root.put("notification", notification);
root.put("data", data);
root.put("registration_ids", recipients);
String result = postToFCM(root.toString());
Log.d(TAG, "Result: " + result);
return result;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(String result) {
try {
JSONObject resultJson = new JSONObject(result);
int success, failure;
success = resultJson.getInt("success");
failure = resultJson.getInt("failure");
Toast.makeText(getCurrentActivity(), "Message Success: " + success + "Message Failed: " + failure, Toast.LENGTH_LONG).show();
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(getCurrentActivity(), "Message Failed, Unknown error occurred.", Toast.LENGTH_LONG).show();
}
}
}.execute();
}
String postToFCM(String bodyString) throws IOException {
RequestBody body = RequestBody.create(JSON, bodyString);
Request request = new Request.Builder()
.url(FCM_MESSAGE_URL)
.post(body)
.addHeader("Authorization", "key=" + SERVER_KEY)
.build();
Response response = mClient.newCall(request).execute();
return response.body().string();
}
I hope Firebase will come with a better solution in future. But till then, I think this is the only way. The other way would be to send topic message or group messaging. But that was not in the scope of the question.
Update:
The JSONArray is defined like this -
JSONArray regArray = new JSONArray(regIds);
regIds is a String array of registration ids, you want to send this message to. Keep in mind that the registration ids must always be in an array, even if you want it to send to a single recipient.
I have also been using direct device to device gcm messaging in my prototype. It has been working very well. We dont have any server. We exchange GCM reg id using sms/text and then communicate using GCM after that. I am putting here code related to GCM handling
**************Sending GCM Message*************
//Sends gcm message Asynchronously
public class GCM_Sender extends IntentService{
final String API_KEY = "****************************************";
//Empty constructor
public GCM_Sender() {
super("GCM_Sender");
}
//Processes gcm send messages
#Override
protected void onHandleIntent(Intent intent) {
Log.d("Action Service", "GCM_Sender Service Started");
//Get message from intent
String msg = intent.getStringExtra("msg");
msg = "\"" + msg + "\"";
try{
String ControllerRegistrationId = null;
//Check registration id in db
if(RegistrationIdAdapter.getInstance(getApplicationContext()).getRegIds().size() > 0 ) {
String controllerRegIdArray[] = RegistrationIdAdapter.getInstance(getApplicationContext()).getRegIds().get(1);
if(controllerRegIdArray.length>0)
ControllerRegistrationId = controllerRegIdArray[controllerRegIdArray.length-1];
if(!ControllerRegistrationId.equalsIgnoreCase("NULL")){
// 1. URL
URL url = new URL("https://android.googleapis.com/gcm/send");
// 2. Open connection
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
// 3. Specify POST method
urlConnection.setRequestMethod("POST");
// 4. Set the headers
urlConnection.setRequestProperty("Content-Type", "application/json");
urlConnection.setRequestProperty("Authorization", "key=" + API_KEY);
urlConnection.setDoOutput(true);
// 5. Add JSON data into POST request body
JSONObject obj = new JSONObject("{\"time_to_live\": 0,\"delay_while_idle\": true,\"data\":{\"message\":" + msg + "},\"registration_ids\":[" + ControllerRegistrationId + "]}");
// 6. Get connection output stream
OutputStreamWriter out = new OutputStreamWriter(urlConnection.getOutputStream());
out.write(obj.toString());
out.close();
// 6. Get the response
int responseCode = urlConnection.getResponseCode();
BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null){
response.append(inputLine);
}
in.close();
Log.d("GCM getResponseCode:", new Integer(responseCode).toString());
}else{
Log.d("GCM_Sender:","Field REGISTRATION_TABLE is null");
}
}else {
Log.d("GCM_Sender:","There is no Registration ID in DB ,please sync devices");
}
} catch (Exception e) {
e.printStackTrace();
//MessageSender.getInstance().sendMessage(msg, Commands.SMS_MESSAGE);
}
}
//Called when service is no longer alive
#Override
public void onDestroy() {
super.onDestroy();
//Do a log that GCM_Sender service has been destroyed
Log.d("Action Service", "GCM_Sender Service Destroyed");
}
}
**************Receiving GCM Message*************
public class GCM_Receiver extends WakefulBroadcastReceiver {
public static final String RETRY_ACTION ="com.google.android.c2dm.intent.RETRY";
public static final String REGISTRATION ="com.google.android.c2dm.intent.REGISTRATION";
public SharedPreferences preferences;
//Processes Gcm message .
#Override
public void onReceive(Context context, Intent intent) {
ComponentName comp = new ComponentName(context.getPackageName(),
GCMNotificationIntentService.class.getName());
//Start GCMNotificationIntentService to handle gcm message asynchronously
startWakefulService(context, (intent.setComponent(comp)));
setResultCode(Activity.RESULT_OK);
/*//Check if DatabaseService is running .
if(!DatabaseService.isServiceRunning) {
Intent dbService = new Intent(context,DatabaseService.class);
context.startService(dbService);
}*/
//Check if action is RETRY_ACTION ,if it is then do gcm registration again .
if(intent.getAction().equals(RETRY_ACTION)) {
String registrationId = intent.getStringExtra("registration_id");
if(TextUtils.isEmpty(registrationId)){
DeviceRegistrar.getInstance().register(context);
}else {
//Save registration id to prefs .
preferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("BLACKBOX_REG_ID",registrationId);
editor.commit();
}
} else if (intent.getAction().equals(REGISTRATION)) {
}
}
}
//Processes gcm messages asynchronously .
public class GCMNotificationIntentService extends IntentService{
public static final int NOTIFICATION_ID = 1;
private NotificationManager mNotificationManager;
String gcmData;
private final String TAG = "GCMNotificationIntentService";
//Constructor with super().
public GCMNotificationIntentService() {
super("GcmIntentService");
}
//Called when startService() is called by its Client .
//Processes gcm messages .
#Override
protected void onHandleIntent(Intent intent) {
Log.d("GCMNotificationIntentService", "GCMNotificationIntentService Started");
Bundle extras = intent.getExtras();
//Get instance of GoogleCloudMessaging .
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
//Get gcm message type .
String messageType = gcm.getMessageType(intent);
if (!extras.isEmpty()) {
if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR
.equals(messageType)) {
sendNotification("Send error: " + extras.toString());
} else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED
.equals(messageType)) {
sendNotification("Deleted messages on server: "
+ extras.toString());
} else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE
.equals(messageType)) {
Log.i(TAG, "Completed work # " + SystemClock.elapsedRealtime());
gcmData = extras.getString("message");
Intent actionService = new Intent(getApplicationContext(),Action.class);
actionService.putExtra("data", gcmData);
//start Action service .
startService(actionService);
//Show push notification .
sendNotification("Action: " + gcmData);
//Process received gcmData.
Log.d(TAG,"Received Gcm Message from Controller : " + extras.getString("message"));
}
}
GCM_Receiver.completeWakefulIntent(intent);
}
//Shows notification on device notification bar .
private void sendNotification(String msg) {
mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
Intent notificationIntent = new Intent(this, BlackboxStarter.class);
//Clicking on GCM notification add new layer of app.
notificationIntent.setFlags( Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
this).setSmallIcon(R.drawable.gcm_cloud)
.setContentTitle("Notification from Controller")
.setStyle(new NotificationCompat.BigTextStyle().bigText(msg))
.setContentText(msg);
mBuilder.setContentIntent(contentIntent);
mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
//Play default notification
try {
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
Ringtone r = RingtoneManager.getRingtone(getApplicationContext(), notification);
r.play();
} catch (Exception e) {
e.printStackTrace();
}
}
//Called when service is no longer be available .
#Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
Log.d("GCMNotificationIntentService", "GCMNotificationIntentService Destroyed");
}
}
According to the new documentation which was updated on October 2, 2018 you must send post request as below
https://fcm.googleapis.com/fcm/send
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA //Server key
{
"to": "sent device's registration token",
"data": {
"hello": "message from someone",
}
}
To get device's registration token extend FirebaseMessagingService and override onNewToken(String token)
For more info refer to doc https://firebase.google.com/docs/cloud-messaging/android/device-group
I am late but above solutions has helped me to write down this simple answer, you can send your message directly to android devices from android application, here is the simple implementation I have done and it works great for me.
compile android volley library
compile 'com.android.volley:volley:1.0.0'
Just copy paste this simple function ;) and your life will become smooth just like knife in butter. :D
public static void sendPushToSingleInstance(final Context activity, final HashMap dataValue /*your data from the activity*/, final String instanceIdToken /*firebase instance token you will find in documentation that how to get this*/ ) {
final String url = "https://fcm.googleapis.com/fcm/send";
StringRequest myReq = new StringRequest(Request.Method.POST,url,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
Toast.makeText(activity, "Bingo Success", Toast.LENGTH_SHORT).show();
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(activity, "Oops error", Toast.LENGTH_SHORT).show();
}
}) {
#Override
public byte[] getBody() throws com.android.volley.AuthFailureError {
Map<String, Object> rawParameters = new Hashtable();
rawParameters.put("data", new JSONObject(dataValue));
rawParameters.put("to", instanceIdToken);
return new JSONObject(rawParameters).toString().getBytes();
};
public String getBodyContentType()
{
return "application/json; charset=utf-8";
}
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("Authorization", "key="+YOUR_LEGACY_SERVER_KEY_FROM_FIREBASE_CONSOLE);
headers.put("Content-Type","application/json");
return headers;
}
};
Volley.newRequestQueue(activity).add(myReq);
}
Note
If you want to send message to topics so you can change parameter instanceIdToken to something like /topics/topicName.
For groups implementation is the same but you just need to take care of parameters. checkout Firebase documentation and you can pass those parameters.
let me know if you face any issue.
I've configured an identity server 3 as an IdP for a project, we have 3 clients: MVC web, IOS and Android.
everything is good for MVC app, using Hybrid flow.
for IOS and Android, using the native oidc client (AppAuth IOS and AppAuth android) is not working, even though I configured the flow as Hybrid with PKCE.
now when I try to make a POC on android using Xamarin, and using IdentityModel.oidcClient everything works as expected, getting access, refresh and id tokens.
when using the AppAuth for IOS and android I am getting the following error:
{"type":0,"code":9,"errorDescription":"Response state param did not match request state"}
any idea what is missing ?
I am suspecting that those two native oidc clients aren't asking for shared secret of the clients, so the flow is corrupted because of that.
The data should be the same in the mobile app and in the Identity Server,
On The Server :
new Client
{
ClientId = "myClientId",
ClientName = "myClientName",
AllowedGrantTypes = GrantTypes.CodeAndClientCredentials,
RequireConsent = false,
ClientSecrets =
{
new Secret("myClientSecret".Sha256())
},
RedirectUris = { "myRedirectUri://callback" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.Phone,
},
AllowOfflineAccess = true
}
On click login in android :
AuthManager authManager = AuthManager.getInstance(this);
AuthorizationService authService = authManager.getAuthService();
Auth auth = authManager.getAuth();
AuthorizationRequest authRequest = new AuthorizationRequest
.Builder(
authManager.getAuthConfig(),
auth.getClientId(),
auth.getResponseType(),
Uri.parse(auth.getRedirectUri()))
.setScope(auth.getScope())
.build();
Intent authIntent = new Intent(this, LoginAuthActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, authRequest.hashCode(), authIntent, 0);
authService.performAuthorizationRequest(
authRequest,
pendingIntent);
Ask for the token :
final AuthorizationResponse resp = AuthorizationResponse.fromIntent(getIntent());
AuthorizationException ex = AuthorizationException.fromIntent(getIntent());
final AuthManager authManager = AuthManager.getInstance(this);
authManager.setAuthState(resp,ex);
if (resp != null) {
ClientSecretPost clientSecretPost = new ClientSecretPost(authManager.getAuth().getClientSecret());
TokenRequest tokenRequest = new TokenRequest
.Builder(authManager.getAuthConfig(), authManager.getAuth().getClientId())
.setAuthorizationCode(resp.authorizationCode)
.setRedirectUri(Uri.parse(authManager.getAuth().getRedirectUri()))
.build();
mAuthService = authManager.getAuthService();
mAuthService.performTokenRequest(tokenRequest, clientSecretPost, new AuthorizationService.TokenResponseCallback() {
#Override public void onTokenRequestCompleted(#Nullable TokenResponse response, #Nullable AuthorizationException ex) {
if(ex == null) {
authManager.updateAuthState(response,ex);
MyApp.Token = authManager.getAuthState().getIdToken();
startService(new Intent(LoginAuthActivity.this, TokenService.class));
Intent mainIntent = new Intent(LoginAuthActivity.this, MainActivity.class);
startActivity(mainIntent);
finish();
}
else{
Intent loginIntent = new Intent(LoginAuthActivity.this, LoginActivity.class);
startActivity(loginIntent);
finish();
}
}
});
// authorization completed
} else {
// authorization failed, check ex for more details
Intent loginIntent = new Intent(LoginAuthActivity.this, LoginActivity.class);
startActivity(loginIntent);
finish();
}
The AuthManager Class :
public class AuthManager {
private static AuthManager instance;
private AuthState mAuthState;
private Auth mAuth;
private AuthorizationServiceConfiguration mAuthConfig;
private SharedPreferencesRepository mSharedPrefRep;
private AuthorizationService mAuthService;
public static AuthManager getInstance(Context context) {
if (instance == null) {
instance = new AuthManager(context);
}
return instance;
}
private AuthManager(Context context){
mSharedPrefRep = new SharedPreferencesRepository(context);
setAuthData();
mAuthConfig = new AuthorizationServiceConfiguration(
Uri.parse(mAuth.getAuthorizationEndpointUri()),
Uri.parse(mAuth.getTokenEndpointUri()),
null);
mAuthState = mSharedPrefRep.getAuthState();
mAuthService = new AuthorizationService(context);
}
public AuthorizationServiceConfiguration getAuthConfig() {
return mAuthConfig;
}
public Auth getAuth() {
if(mAuth == null){
setAuthData();
}
return mAuth;
}
public AuthState getAuthState(){
return mAuthState;
}
public void updateAuthState(TokenResponse response, AuthorizationException ex){
mAuthState.update(response,ex);
mSharedPrefRep.saveAuthState(mAuthState);
}
public void setAuthState(AuthorizationResponse response, AuthorizationException ex){
if(mAuthState == null)
mAuthState = new AuthState(response,ex);
mSharedPrefRep.saveAuthState(mAuthState);
}
public AuthorizationService getAuthService(){
return mAuthService;
}
private void setAuthData(){
mAuth = new Auth();
mAuth.setClientId(BuildConfig.CLIENT_ID);
mAuth.setAuthorizationEndpointUri(BuildConfig.AUTHORIZSTION_END_POINT_URI);
mAuth.setClientSecret(BuildConfig.CLIENT_SECRET);
mAuth.setRedirectUri(BuildConfig.REDIRECT_URI);
mAuth.setScope(BuildConfig.SCOPE);
mAuth.setTokenEndpointUri(BuildConfig.TOKEN_END_POINT_URI);
mAuth.setResponseType(BuildConfig.RESPONSE_TYPE);
}
}
The Service here will ask for the refresh token.
I have made a sample using Identity Server 4 with AppAuth-Android you can check it here
We have an open issue for hybrid flow support on AppAuth-Android here. The main issue with this is that the hybrid flow is a poor fit for mobile applications, as it would require repeatedly triggering a web flow via SafariViewController / CustomTab every time the access token expires. Acquiring a refresh token to allow background update of access tokens is better for native apps.
As IdentityServer3 is a certified OpenID Connect implementation, you should be able to use the authorization code flow to obtain a refresh token.
I have been trying to read the official docs and guides about how to send message from one device to another. I have saved registration token of both devices in the Real Time Database, thus I have the registration token of another device.
I have tried the following way to send the message
RemoteMessage message = new RemoteMessage.Builder(getRegistrationToken())
.setMessageId(incrementIdAndGet())
.addData("message", "Hello")
.build();
FirebaseMessaging.getInstance().send(message);
However this is not working. The other device doesn't receive any message. I am not even sure, if I can use upstream message sending to conduct device to device communication.
PS: I just want to know if device-to-device messaging is possible using FCM? If yes, then is the code I used have some issue? If yes, then what is the correct way.
Update:
My question was to ask whether device to device messaging without using any separate server other than firebase could messaging is possible or not, if yes than how, since there's no documentation about it. I do not understand what is left to explain here? Anyways I got the answer and will update it as an answer once the question gets reopened.
Firebase has two features to send messages to devices:
the Notifications panel in your Firebase Console allows you to send notifications to specific devices, groups of users, or topics that users subscribed to.
by calling Firebase Cloud Messaging API, you can send messages with whatever targeting strategy you prefer. Calling the FCM API requires access to your Server key, which you should never expose on client devices. That's why you should always run such code on an app server.
The Firebase documentation shows this visually:
Sending messages from one device directly to another device is not supported through the Firebase Cloud Messaging client-side SDKs.
Update: I wrote a blog post detailing how to send notifications between Android devices using Firebase Database, Cloud Messaging and Node.js.
Update 2: You can now also use Cloud Functions for Firebase to send messages securely, without spinning up a server. See this sample use-case to get started. If you don't want to use Cloud Functions, you can run the same logic on any trusted environment you already have, such as your development machine, or a server you control.
Warning There is a very important reason why we don't mention this approach anywhere. This exposes your server key in the APK that
you put on every client device. It can (and thus will) be taken from
there and may lead to abuse of your project. I highly recommend
against taking this approach, except for apps that you only put on
your own devices. – Frank van Puffelen
Ok, so the answer by Frank was correct that Firebase does not natively support device to device messaging. However there's one loophole in that. The Firebase server doesn't identify whether you have send the request from an actual server or are you doing it from your device.
So all you have to do is send a Post Request to Firebase's messaging server along with the Server Key. Just keep this in mind that the server key is not supposed to be on the device, but there's no other option if you want device-to-device messaging using Firebase Messaging.
I am using OkHTTP instead of default way of calling the Rest API. The code is something like this -
public static final String FCM_MESSAGE_URL = "https://fcm.googleapis.com/fcm/send";
OkHttpClient mClient = new OkHttpClient();
public void sendMessage(final JSONArray recipients, final String title, final String body, final String icon, final String message) {
new AsyncTask<String, String, String>() {
#Override
protected String doInBackground(String... params) {
try {
JSONObject root = new JSONObject();
JSONObject notification = new JSONObject();
notification.put("body", body);
notification.put("title", title);
notification.put("icon", icon);
JSONObject data = new JSONObject();
data.put("message", message);
root.put("notification", notification);
root.put("data", data);
root.put("registration_ids", recipients);
String result = postToFCM(root.toString());
Log.d(TAG, "Result: " + result);
return result;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(String result) {
try {
JSONObject resultJson = new JSONObject(result);
int success, failure;
success = resultJson.getInt("success");
failure = resultJson.getInt("failure");
Toast.makeText(getCurrentActivity(), "Message Success: " + success + "Message Failed: " + failure, Toast.LENGTH_LONG).show();
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(getCurrentActivity(), "Message Failed, Unknown error occurred.", Toast.LENGTH_LONG).show();
}
}
}.execute();
}
String postToFCM(String bodyString) throws IOException {
RequestBody body = RequestBody.create(JSON, bodyString);
Request request = new Request.Builder()
.url(FCM_MESSAGE_URL)
.post(body)
.addHeader("Authorization", "key=" + SERVER_KEY)
.build();
Response response = mClient.newCall(request).execute();
return response.body().string();
}
I hope Firebase will come with a better solution in future. But till then, I think this is the only way. The other way would be to send topic message or group messaging. But that was not in the scope of the question.
Update:
The JSONArray is defined like this -
JSONArray regArray = new JSONArray(regIds);
regIds is a String array of registration ids, you want to send this message to. Keep in mind that the registration ids must always be in an array, even if you want it to send to a single recipient.
I have also been using direct device to device gcm messaging in my prototype. It has been working very well. We dont have any server. We exchange GCM reg id using sms/text and then communicate using GCM after that. I am putting here code related to GCM handling
**************Sending GCM Message*************
//Sends gcm message Asynchronously
public class GCM_Sender extends IntentService{
final String API_KEY = "****************************************";
//Empty constructor
public GCM_Sender() {
super("GCM_Sender");
}
//Processes gcm send messages
#Override
protected void onHandleIntent(Intent intent) {
Log.d("Action Service", "GCM_Sender Service Started");
//Get message from intent
String msg = intent.getStringExtra("msg");
msg = "\"" + msg + "\"";
try{
String ControllerRegistrationId = null;
//Check registration id in db
if(RegistrationIdAdapter.getInstance(getApplicationContext()).getRegIds().size() > 0 ) {
String controllerRegIdArray[] = RegistrationIdAdapter.getInstance(getApplicationContext()).getRegIds().get(1);
if(controllerRegIdArray.length>0)
ControllerRegistrationId = controllerRegIdArray[controllerRegIdArray.length-1];
if(!ControllerRegistrationId.equalsIgnoreCase("NULL")){
// 1. URL
URL url = new URL("https://android.googleapis.com/gcm/send");
// 2. Open connection
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
// 3. Specify POST method
urlConnection.setRequestMethod("POST");
// 4. Set the headers
urlConnection.setRequestProperty("Content-Type", "application/json");
urlConnection.setRequestProperty("Authorization", "key=" + API_KEY);
urlConnection.setDoOutput(true);
// 5. Add JSON data into POST request body
JSONObject obj = new JSONObject("{\"time_to_live\": 0,\"delay_while_idle\": true,\"data\":{\"message\":" + msg + "},\"registration_ids\":[" + ControllerRegistrationId + "]}");
// 6. Get connection output stream
OutputStreamWriter out = new OutputStreamWriter(urlConnection.getOutputStream());
out.write(obj.toString());
out.close();
// 6. Get the response
int responseCode = urlConnection.getResponseCode();
BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null){
response.append(inputLine);
}
in.close();
Log.d("GCM getResponseCode:", new Integer(responseCode).toString());
}else{
Log.d("GCM_Sender:","Field REGISTRATION_TABLE is null");
}
}else {
Log.d("GCM_Sender:","There is no Registration ID in DB ,please sync devices");
}
} catch (Exception e) {
e.printStackTrace();
//MessageSender.getInstance().sendMessage(msg, Commands.SMS_MESSAGE);
}
}
//Called when service is no longer alive
#Override
public void onDestroy() {
super.onDestroy();
//Do a log that GCM_Sender service has been destroyed
Log.d("Action Service", "GCM_Sender Service Destroyed");
}
}
**************Receiving GCM Message*************
public class GCM_Receiver extends WakefulBroadcastReceiver {
public static final String RETRY_ACTION ="com.google.android.c2dm.intent.RETRY";
public static final String REGISTRATION ="com.google.android.c2dm.intent.REGISTRATION";
public SharedPreferences preferences;
//Processes Gcm message .
#Override
public void onReceive(Context context, Intent intent) {
ComponentName comp = new ComponentName(context.getPackageName(),
GCMNotificationIntentService.class.getName());
//Start GCMNotificationIntentService to handle gcm message asynchronously
startWakefulService(context, (intent.setComponent(comp)));
setResultCode(Activity.RESULT_OK);
/*//Check if DatabaseService is running .
if(!DatabaseService.isServiceRunning) {
Intent dbService = new Intent(context,DatabaseService.class);
context.startService(dbService);
}*/
//Check if action is RETRY_ACTION ,if it is then do gcm registration again .
if(intent.getAction().equals(RETRY_ACTION)) {
String registrationId = intent.getStringExtra("registration_id");
if(TextUtils.isEmpty(registrationId)){
DeviceRegistrar.getInstance().register(context);
}else {
//Save registration id to prefs .
preferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("BLACKBOX_REG_ID",registrationId);
editor.commit();
}
} else if (intent.getAction().equals(REGISTRATION)) {
}
}
}
//Processes gcm messages asynchronously .
public class GCMNotificationIntentService extends IntentService{
public static final int NOTIFICATION_ID = 1;
private NotificationManager mNotificationManager;
String gcmData;
private final String TAG = "GCMNotificationIntentService";
//Constructor with super().
public GCMNotificationIntentService() {
super("GcmIntentService");
}
//Called when startService() is called by its Client .
//Processes gcm messages .
#Override
protected void onHandleIntent(Intent intent) {
Log.d("GCMNotificationIntentService", "GCMNotificationIntentService Started");
Bundle extras = intent.getExtras();
//Get instance of GoogleCloudMessaging .
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
//Get gcm message type .
String messageType = gcm.getMessageType(intent);
if (!extras.isEmpty()) {
if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR
.equals(messageType)) {
sendNotification("Send error: " + extras.toString());
} else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED
.equals(messageType)) {
sendNotification("Deleted messages on server: "
+ extras.toString());
} else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE
.equals(messageType)) {
Log.i(TAG, "Completed work # " + SystemClock.elapsedRealtime());
gcmData = extras.getString("message");
Intent actionService = new Intent(getApplicationContext(),Action.class);
actionService.putExtra("data", gcmData);
//start Action service .
startService(actionService);
//Show push notification .
sendNotification("Action: " + gcmData);
//Process received gcmData.
Log.d(TAG,"Received Gcm Message from Controller : " + extras.getString("message"));
}
}
GCM_Receiver.completeWakefulIntent(intent);
}
//Shows notification on device notification bar .
private void sendNotification(String msg) {
mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
Intent notificationIntent = new Intent(this, BlackboxStarter.class);
//Clicking on GCM notification add new layer of app.
notificationIntent.setFlags( Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
this).setSmallIcon(R.drawable.gcm_cloud)
.setContentTitle("Notification from Controller")
.setStyle(new NotificationCompat.BigTextStyle().bigText(msg))
.setContentText(msg);
mBuilder.setContentIntent(contentIntent);
mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
//Play default notification
try {
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
Ringtone r = RingtoneManager.getRingtone(getApplicationContext(), notification);
r.play();
} catch (Exception e) {
e.printStackTrace();
}
}
//Called when service is no longer be available .
#Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
Log.d("GCMNotificationIntentService", "GCMNotificationIntentService Destroyed");
}
}
According to the new documentation which was updated on October 2, 2018 you must send post request as below
https://fcm.googleapis.com/fcm/send
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA //Server key
{
"to": "sent device's registration token",
"data": {
"hello": "message from someone",
}
}
To get device's registration token extend FirebaseMessagingService and override onNewToken(String token)
For more info refer to doc https://firebase.google.com/docs/cloud-messaging/android/device-group
I am late but above solutions has helped me to write down this simple answer, you can send your message directly to android devices from android application, here is the simple implementation I have done and it works great for me.
compile android volley library
compile 'com.android.volley:volley:1.0.0'
Just copy paste this simple function ;) and your life will become smooth just like knife in butter. :D
public static void sendPushToSingleInstance(final Context activity, final HashMap dataValue /*your data from the activity*/, final String instanceIdToken /*firebase instance token you will find in documentation that how to get this*/ ) {
final String url = "https://fcm.googleapis.com/fcm/send";
StringRequest myReq = new StringRequest(Request.Method.POST,url,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
Toast.makeText(activity, "Bingo Success", Toast.LENGTH_SHORT).show();
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(activity, "Oops error", Toast.LENGTH_SHORT).show();
}
}) {
#Override
public byte[] getBody() throws com.android.volley.AuthFailureError {
Map<String, Object> rawParameters = new Hashtable();
rawParameters.put("data", new JSONObject(dataValue));
rawParameters.put("to", instanceIdToken);
return new JSONObject(rawParameters).toString().getBytes();
};
public String getBodyContentType()
{
return "application/json; charset=utf-8";
}
#Override
public Map<String, String> getHeaders() throws AuthFailureError {
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("Authorization", "key="+YOUR_LEGACY_SERVER_KEY_FROM_FIREBASE_CONSOLE);
headers.put("Content-Type","application/json");
return headers;
}
};
Volley.newRequestQueue(activity).add(myReq);
}
Note
If you want to send message to topics so you can change parameter instanceIdToken to something like /topics/topicName.
For groups implementation is the same but you just need to take care of parameters. checkout Firebase documentation and you can pass those parameters.
let me know if you face any issue.
I'm trying to use Retrofit (2.0.0-beta3), but when using an Authenticator to add a token, I can't seem to get the data from the synchronous call. Our logging on the back-end just shows a lot of login attempts, but I can't get the data from the body to actually add to the header.
public static class TokenAuthenticator implements Authenticator {
#Override
public Request authenticate(Route route, Response response) throws IOException {
// Refresh your access_token using a synchronous api request
UserService userService = createService(UserService.class);
Call<Session> call = userService.emailLogin(new Credentials("handle", "pass"));
// This call is made correctly, as it shows up on the back-end.
Session body = call.execute().body();
// This line is never hit.
Logger.d("Session token: " + body.token);
// Add new header to rejected request and retry it
return response.request().newBuilder()
.header("Auth-Token", body.token)
.build();
}
}
I'm not exactly too sure on why it's not even printing anything out. Any tips on how to solve this issue would be greatly appreciated, thanks for taking the time to help.
These are the sources I've been reading on how to implement Retrofit.
Using Authenticator:
https://stackoverflow.com/a/31624433/3106174
https://github.com/square/okhttp/wiki/Recipes#handling-authentication
Making synchronous calls with Retrofit 2:
https://futurestud.io/blog/retrofit-synchronous-and-asynchronous-requests
I managed to get a decent solution using the TokenAuthenticator and an Interceptor and thought I'd share the idea as it may help some others.
Adding the 'TokenInterceptor' class that handles adding the token to the header is the token exists, and the 'TokenAuthenticator' class handles the case when there is no token, and we need to generate one.
I'm sure there are some better ways to implement this, but it's a good starting point I think.
public static class TokenAuthenticator implements Authenticator {
#Override
public Request authenticate( Route route, Response response) throws IOException {
...
Session body = call.execute().body();
Logger.d("Session token: " + body.token);
// Storing the token somewhere.
session.token = body.token;
...
}
private static class TokenInterceptor implements Interceptor {
#Override
public Response intercept( Chain chain ) throws IOException {
Request originalRequest = chain.request();
// Nothing to add to intercepted request if:
// a) Authorization value is empty because user is not logged in yet
// b) There is already a header with updated Authorization value
if (authorizationTokenIsEmpty() || alreadyHasAuthorizationHeader(originalRequest)) {
return chain.proceed(originalRequest);
}
// Add authorization header with updated authorization value to intercepted request
Request authorisedRequest = originalRequest.newBuilder()
.header("Auth-Token", session.token )
.build();
return chain.proceed(authorisedRequest);
}
}
Source:
http://lgvalle.xyz/2015/07/27/okhttp-authentication/
I have similar authenticator and it works with 2.0.0-beta2.
If you get lots of login attempts from you Authenticator, I suggest make sure that when you make the synchronous call, you are not using Authenticator with that call.
That could end up in loop, if also your "emailLogin" fails.
Also I would recommend adding loggingInterceptor to see all trafic to server: Logging with Retrofit 2
I know it's a late answer but for anyone still wondering how to Add / Refresh token with Retrofit 2 Authenticator, here is a working solution:
Note: preferenceHelper is your Preference Manager class where you set/get your shared preferences.
public class AuthenticationHelper implements Authenticator {
private static final String HEADER_AUTHORIZATION = "Authorization";
private static final int REFRESH_TOKEN_FAIL = 403;
private Context context;
AuthenticationHelper(#ApplicationContext Context context) {
this.context = context;
}
#Override
public Request authenticate(#NonNull Route route, #NonNull Response response) throws IOException {
// We need to have a token in order to refresh it.
String token = preferencesHelper.getAccessToken();
if (token == null)
return null;
synchronized (this) {
String newToken = preferencesHelper.getAccessToken();
if (newToken == null)
return null;
// Check if the request made was previously made as an authenticated request.
if (response.request().header(HEADER_AUTHORIZATION) != null) {
// If the token has changed since the request was made, use the new token.
if (!newToken.equals(token)) {
return response.request()
.newBuilder()
.removeHeader(HEADER_AUTHORIZATION)
.addHeader(HEADER_AUTHORIZATION, "Bearer " + newToken)
.build();
}
JsonObject refreshObject = new JsonObject();
refreshObject.addProperty("refreshToken", preferencesHelper.getRefreshToken());
retrofit2.Response<UserToken> tokenResponse = apiService.refreshToken(refreshObject).execute();
if (tokenResponse.isSuccessful()) {
UserToken userToken = tokenResponse.body();
if (userToken == null)
return null;
preferencesHelper.saveAccessToken(userToken.getToken());
preferencesHelper.saveRefreshToken(userToken.getRefreshToken());
// Retry the request with the new token.
return response.request()
.newBuilder()
.removeHeader(HEADER_AUTHORIZATION)
.addHeader(HEADER_AUTHORIZATION, "Bearer " + userToken.getToken())
.build();
} else {
if (tokenResponse.code() == REFRESH_TOKEN_FAIL) {
logoutUser();
}
}
}
}
return null;
}
private void logoutUser() {
// logout user
}
}
Also note:
preferenceHelper and apiService needs to be provided in some way.
This is not an example that will work for all systems and api's but an example in how adding and refreshing the token should be done using Retrofit 2 Authenticator