I am using Google Cloud Messaging with XMPP in order to have both downstream and upstream messages.
Only client side I get a token by doing this on a worker thread:
InstanceID instanceID = InstanceID.getInstance(this);
try {
String token = instanceID.getToken(getString(R.string.gcm_senderID), GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
send_token(token, getString(R.string.gcm_senderID));
} catch (IOException e) {
e.printStackTrace();
}
I then send this token over to the server where it is received. I am able to send messages to the client with this token.
Then I can send an upstream message on the client side with this:
new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground(Void... params) {
String msg;
Bundle data = new Bundle();
data.putString("message", message);
try {
messenger.send(getString(R.string.gcm_senderID) + "#gcm.googleapis.com", messageId.addAndGet(1) + "", data);
} catch (IOException e) {
e.printStackTrace();
}
msg = "Sent message";
return msg;
}
}.execute(null, null, null);
In the upstream message sent from the client, there is a from field, that seems to be a token as well. If I send a message to this from the server side, my phone receives it as well.
What confuses me is that the token in the from field is not equal to the one generated by the InstanceID service.
The first 18 characters or so are equal, but after that they are very different. As such, is there a good way to identify what device sent what message?
I could store the token generated by the Instance ID each time in the Bundle, but I was wondering if there might be any way to make the from field of the upstream message be consistent with the generated ID.
Edit: Using the deprecated register function, I was able to get a consistent registration ID.
String token = GoogleCloudMessaging.getInstance().register(getString(R.string.gcm_senderID));
But is there a way to do this with InstanceID?
Calling GoogleCloudMessaging.getInstance(context).register(senderId) instead of getToken(senderId, "GCM") seems to resolve the issue, the XMPP server will then receive the correct token, every time, in the "from" property of the upstream message.
My device is running CyanogenMod, so the Google Play services app doesn't update automatically. Since the old register() work, this issue is likely caused by a bug in the google-play-services_lib when talking to an older version of the GMS app.
I've answered instead of comment with the vain hopes of an Google dev seeing this.
Related
I'm developing an Android app that consumes data from my own REST API server. I want to use Firebase authentication because it allows the user to login using Google, Facebook, Twitter... in a very simple way.
But I'm not sure how to use ID tokens:
Because ID tokens have expiration date, should I call getToken method on every request in the client app, so I'm sure I'm sending a valid token every time?
Should I call verifyIdToken in the server each time I receive a request from the client app?
I don't know what these methods (getToken and verifyIdToken) do under the hood, and because they are asynchronous, I fear they are doing a request to Firebase servers on every call. So I think that making 2 request to Firebase servers in each of my requests is not the way to go...
Both getToken() and VerifyIdToken() are designed to be called for every outgoing/incoming request.
1) Although getToken() is asynchronous, the Firebase Android SDK actually caches the current Firebase user token in local storage. So long as the cached token is still valid (i.e. within one hour since issued), getToken() returns the token immediately. Only when the cached token expires does the SDK fetch a new token from remote Firebase server.
2) VerifyIdToken() is also optimized for performance. It caches the Firebase token public cert (valid for 6 hours) which is used to validate the token signature on local machine. No RPC is involved except for downloading the public cert.
You refresh token each time when is no more valid. And yes, you should verify token on server-side each time. If is no more valid, you send 401 error code with error message (if you want). Verify token is used when you refresh token, and token is append to each request. If you use OkHttp you can create an interceptor that is adding token in header to each request and also can refresh token when error code is 401.
POST https://YOUR_AUTH0_DOMAIN/delegation
Content-Type: 'application/json'
{
"client_id": "YOUR_CLIENT_ID",
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"refresh_token": "your_refresh_token",
"api_type": "app"
}
From what you have explained in the question, I guess you are talking about cross client resource access using Google sign in. And specifically you seem to be interested in obtaining the Id token once and use it without having to obtain on each subsequent API call.
This more or less is synonymous with the offline access mechanism.
In offline access, the Client I.e. the Android app asks for user authorisation for requested scopes. Upon authorisation, instead of issuing an access token, auth server returns a short lived authorisation code which can be used to generate an access token and refresh token.
The client then can pass the authorisation code to the backend over a secure connection. Backend server can retrieve the author token and refresh token and store them in a secure location. The access token is short lived and can be used to access scoped resources for a short time and refreshed from time to time using the refresh token. The refresh token does not expire but can be revoked. If revoked, server app should ask the client app to re-fetch the author code.
Please go through this link which details the complete infrastructure along with the steps to be followed both by client and server app -
https://developers.google.com/identity/protocols/CrossClientAuth
Now coming to your question, you should use a slightly different API to obtain the auth code. Check out this API -
https://developers.google.com/android/reference/com/google/android/gms/auth/api/signin/GoogleSignInOptions.Builder.html#requestServerAuthCode(java.lang.String)
Sample code at - https://developers.google.com/identity/sign-in/android/offline-access
Use below code in your application class and regId is the value holder for your device token.
private void checkPlayService() {
// Check device for Play Services APK. If check succeeds, proceed with
// GCM registration.
if (checkPlayServices()) {
GoogleCloudMessaging googleCloudMessaging = GoogleCloudMessaging.getInstance(activity);
regId = getRegistrationId();
if (TextUtils.isEmpty(regId)) {
registerInBackground();
}
} else {
Log.i(TAG, "No valid Google Play Services APK found.");
}
}
private String getRegistrationId() {
String registrationId = sp.getString(Consts.PROPERTY_REG_ID, "");
if (TextUtils.isEmpty(registrationId)) {
Log.i(TAG, "Registration not found.");
return "";
}
// Check if app was updated; if so, it must clear the registration ID
// since the existing regID is not guaranteed to work with the new
// app version.
int registeredVersion = sp.getInt(PROPERTY_APP_VERSION,0);
int currentVersion = getAppVersion();
if (registeredVersion != currentVersion) {
Log.i(TAG, "App version changed.");
return "";
}
return registrationId;
}
private void registerInBackground() {
new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground(Void... params) {
String msg = "";
try {
if (googleCloudMessaging == null) {
googleCloudMessaging = GoogleCloudMessaging.getInstance(activity);
}
regId = googleCloudMessaging.register(Consts.PROJECT_NUMBER);
msg = "Device registered, registration ID=" + regId;
Log.e("GCMID",msg);
storeRegistrationId(regId);
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
}
return msg;
}
#Override
protected void onPostExecute(String msg) {
Log.i(TAG, msg + "\n");
}
}.execute(null, null, null);
}
private void storeRegistrationId(String regId) {
int appVersion = getAppVersion();
Log.i(TAG, "Saving regId on app version " + appVersion);
sp.edit().putString(Consts.PROPERTY_REG_ID, regId).commit();
sp.edit().putInt(PROPERTY_APP_VERSION, appVersion).commit();
}
I followed Google Cloud Messaging (GCM) with local device groups on Android gives HTTP Error code 401 to manage local device groups on Android and successfully got a notification key, but when I send message to the notification key, I never get the message back.
Has anyone ever got this work?
My send code is like:
public void sendMessage(View view) {
AsyncTask<Void, Void, String> task = new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground(Void... params) {
try {
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(getApplicationContext());
String to = notificationKey; // the notification key
AtomicInteger msgId = new AtomicInteger();
String id = Integer.toString(msgId.incrementAndGet());
Bundle data = new Bundle();
data.putString("hello", "world");
gcm.send(to, id, data);
Log.e(TAG, "sendMessage done.");
} catch (Exception ex) {
Log.e(TAG, ex.toString());
}
return null;
}
};
task.execute();
}
It seems there's a misunderstanding about the GCM concept. The app server is an integral part of GCM messaging.
The server side of Google Cloud Messaging (GCM) consists of two
components:
GCM connection servers provided by Google. These servers take messages
from an app server and send them to a client app running on a device.
Google provides connection servers for HTTP and XMPP.
An application
server that you must implement in your environment. This application
server sends data to a client app via the chosen GCM connection
server, using the appropriate XMPP or HTTP protocol.
Try the Android GCM Playground to get a better understanding of this.
Here's a snippet:
public void sendMessage() {
String senderId = getString(R.string.gcm_defaultSenderId);
if (!("".equals(senderId))) {
String text = upstreamMessageField.getText().toString();
if (text == "") {
showToast("Please enter a message to send");
return;
}
// Create the bundle for sending the message.
Bundle message = new Bundle();
message.putString(RegistrationConstants.ACTION, RegistrationConstants.UPSTREAM_MESSAGE);
message.putString(RegistrationConstants.EXTRA_KEY_MESSAGE, text);
try {
gcm.send(GcmPlaygroundUtil.getServerUrl(senderId),
String.valueOf(System.currentTimeMillis()), message);
showToast("Message sent successfully");
} catch (IOException e) {
Log.e(TAG, "Message failed", e);
showToast("Upstream FAILED");
}
}
}
The to field of the send method represents the sender ID of your project. You cannot use this method to send messages to Instance ID tokens (other devices), Device to Device messaging is not currently supported by GCM.
You are correct to avoid including the API key in your client app, so currently you will need an app server to send these types of messages.
In my Xamarin Android app I call
var instanceID = InstanceID.GetInstance(this);
string token = instanceID.GetToken("xxx", GoogleCloudMessaging.InstanceIdScope, null);
and I get a token in return in the format e63498f:oijafa89fjaasi...
In my c# program I call
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "key=xxxx");
//Get current connection
string url = "https://gcm-http.googleapis.com/gcm/send";
var message = new JObject();
var data = new JObject();
data.Add("message", "hello from csharp");
message.Add("to", "e63498f:oijafa89fjaasi...");
message.Add("data", data);
HttpResponseMessage response = null;
try
{
response = await client.PostAsync(url, new StringContent(message.ToString(), Encoding.Default, "application/json"));
}
catch (Exception exp)
{
MessageBox.Show(exp.Message);
return;
}
//Handle errors
if (!response.IsSuccessStatusCode)
MessageBox.Show("Error: " + response.ToString());
string responseBody = await response.Content.ReadAsStringAsync();
textBox2.Text = responseBody;
I get the response:
{Text = "{\"multicast_id\":xxxx,\"success\":0,\"failure\":1,\"canonical_ids\":0,\"results\":[{\"error\":\"NotRegistered\"}]}"}
I have tryed alot of things but I cannot get it working. If I use the old GCM (gcm.register) there is no error message, but I don't want to use deprecated functionality. Why does GCM say that the token is not registrered when I just got a token returned from GCM? (The app is of course open while I do the test). Do I need to call some sort of method to actually register the token?
Found this similar thread regarding GCM in the Xamarin forums that discusses the same issue. Mentioned in the thread "The problem seems to be that deployments to the VM may be triggering the uninstall scenario.". A workaround/solution is also included:
"The solution I came up with was to track tokens where I receive a Not Registered response on the server, if a device indicates they want to use that token I respond with a send-a-new-token response. The way to accomplish this is to delete the InstanceId and then trigger the registration service
Google cloud message 'Not Registered' failure and unsubscribe best practices?"
Care to try it out. Let me know if it works.
I'm working on a mobile application and i'm on Push Notification.
I can retrieve a token from a phone (apple or android) for send a push but i have a question :
This token is always the same ? If a get one time the token, i need to check if the token change ?
From apple documentation,
The form of this phase of token trust ensures that only APNs generates
the token which it will later honor, and it can assure itself that a
token handed to it by a device is the same token that it previously
provisioned for that particular device—and only for that device.
If the user restores backup data to a new device or reinstalls the
operating system, the device token changes.
So, its always good to update the server with the token received from APN. As part of optimisation, if you are receiving the same token, there is no need to update the server.
For Android:
It depends on your implementation, but what is recommended from google is that the registration id, can be changed after the app is updated...
Everytime the registration id is changed, the client should update the server with the new value.
check: http://developer.android.com/google/gcm/client.html#sample-register
if (checkPlayServices()) {
gcm = GoogleCloudMessaging.getInstance(this);
regid = getRegistrationId(context);
if (regid.isEmpty()) {
registerInBackground();
}
} else {
Log.i(TAG, "No valid Google Play Services APK found.");
}
private void registerInBackground() {
new AsyncTask() {
#Override
protected String doInBackground(Void... params) {
String msg = "";
try {
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(context);
}
regid = gcm.register(SENDER_ID);
msg = "Device registered, registration ID=" + regid;
// You should send the registration ID to your server over HTTP,
// so it can use GCM/HTTP or CCS to send messages to your app.
// The request to your server should be authenticated if your app
// is using accounts.
sendRegistrationIdToBackend();
// For this demo: we don't need to send it because the device
// will send upstream messages to a server that echo back the
// message using the 'from' address in the message.
// Persist the regID - no need to register again.
storeRegistrationId(context, regid);
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
// If there is an error, don't just keep trying to register.
// Require the user to click a button again, or perform
// exponential back-off.
}
return msg;
}
#Override
protected void onPostExecute(String msg) {
mDisplay.append(msg + "\n");
}
}.execute(null, null, null);
...
}
I'm trying to implement google's GCM on my android application and I came across a question about the .register() function.
private void registerInBackground() {
new AsyncTask<Void, Void, String>() {
#Override
protected String doInBackground(Void... params) {
String msg = "";
try {
if (gcm == null) {
gcm = GoogleCloudMessaging.getInstance(context);
}
regid = gcm.register(SENDER_ID);
Log.d(TAG, "registered gcm");
msg = "Device registered, registration ID=" + regid;
Log.d(TAG, "Current Device's Registration ID is: "+msg);
// You should send the registration ID to your server over HTTP, so it
// can use GCM/HTTP or CCS to send messages to your app.
// sendRegistrationIdToBackend();
// For this demo: we don't need to send it because the device will send
// upstream messages to a server that echo back the message using the
// 'from' address in the message.
// Persist the regID - no need to register again.
storeRegistrationId(context, regid);
} catch (IOException ex) {
msg = "Error :" + ex.getMessage();
// If there is an error, don't just keep trying to register.
// Require the user to click a button again, or perform
// exponential back-off.
}
return msg;
}
For regid = gcm.register(SENDER_ID);, what type of connection would the application use to send over to GCM in order to register the SENDER_ID? Does it only do it via wi-fi or do I need to establish a server in order to register with the GCM. My main question is, how does the client app register with GCM to obtain that registration ID?
AFAIR, no it does not need a custom server from your side to receive the Registration ID. Google servers sends that to the user device. You( your custom server) will need this ID later for requesting Google to send Push Notification to the user device. The user device will be identified using this particular registration ID. So your server needs to store that. You can have some sort of mapping of registration ID to some friendly name on your server side.
In short, you need a custom server to which you will send the registration id from the user device when it receives from Google. But, you don't need it for (.register) function.