Push notifications with firebase - firebase

I'm getting this error when i test with token which I got from firebase
Error -The request's Authentication (Server-) Key contained an invalid or malformed FCM-Token (a.k.a. IID-Token)
these are the codes that i used to get token from firebase.
async getToken() {
let fcmToken = await AsyncStorage.getItem('fcmToken');
console.log("before fcmToken: ", fcmToken);
if (!fcmToken) {
fcmToken = await firebase.messaging().getToken();
if (fcmToken) {
console.log("after fcmToken: ", fcmToken);
await AsyncStorage.setItem('fcmToken', fcmToken);
}
}
}
async requestPermission() {
firebase.messaging().requestPermission()
.then(() => {
this.getToken();
})
.catch(error => {
console.log('permission rejected');
});
}
async checkPermission() {
firebase.messaging().hasPermission()
.then(enabled => {
if (enabled) {
console.log("Permission granted");
this.getToken();
} else {
console.log("Request Permission");
this.requestPermission();
}
});
}`
But I got this Error 401 when I test with APNS & GCM Tester Online
https://pushtry.com/
Please may I have any methods to get push notification for react native android application?

You can use postman. Make a post request to this url "https://fcm.googleapis.com/fcm/send".
you need to send an object like this:
{
"data":
{
"title": "your title",
"body": "your body"
},
"notification": {
"title": "your title",
"body": "your body"
},
"registration_ids" : ["czf_nv_t1JA:APA91bGOyY3lTSG9b7Nr71xVo_Xn5RrxOIOwVfnKDv2OBanQjx1eQQyqdM3PFOd1Pjapm_DWn1R327iDyEwEeXjflJ3DyaGFF4iXmqot-OsyDt-Iz99Lu3MZZTvOSFIPiYgiaGHP5ByO"]
}
and registration_ids is your token.Also you need to set a header for your request with key Authorization and it's value comes from firebase console.for finding that you can check this https://developer.clevertap.com/docs/find-your-fcm-sender-id-fcm-server-api-key

Make sure in Authorization value is as per your firebase cloud messaging server key.
Reference link:- https://www.djamware.com/post/5c6ccd1f80aca754f7a9d1ec/push-notification-using-ionic-4-and-firebase-cloud-messaging

Related

CustomToken not working when issued through "createCustomToken" method

I am using Firebase authentication for my application and using it to authenicate users to a back-end API using JWT tokens. On the API back-end I've configured the JWT-secret, which is the asymmetric keys pulled from this url:
https://www.googleapis.com/service_accounts/v1/jwk/securetoken#system.gserviceaccount.com
This is all working fine. I recently needed to create a cloud function, which needs to call the API back-end as well. To do this, I'm using the functionality to create a Custom Token found here:
https://firebase.google.com/docs/auth/admin/create-custom-tokens
This creates my token with correct custom claims
let additionalClaims = {
'x-hasura-default-role': 'admin',
'x-hasura-allowed-roles': ['user', 'admin']
}
admin.auth().createCustomToken(userId,additionalClaims).then(function (customToken) {
console.log(customToken);
response.end(JSON.stringify({
token: customToken
}))
})
.catch(function (error) {
console.log('Error creating custom token:', error);
});
however, when I try to use it against the back-end API, I get the "JWTInvalidSignature" error. In my cloud function, I specify the service account that is in my firebase project, but it doesn't seem to help. When I view the two tokens decoded, they definitely appear coming from different services.
CustomToken
{
"aud":
"https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
"iat": 1573164629,
"exp": 1573168229,
"iss": "firebase-adminsdk-r2942#postgrest-b4c8c.iam.gserviceaccount.com",
"sub": "firebase-adminsdk-r2942#postgrest-b4c8c.iam.gserviceaccount.com",
"uid": "mikeuserid",
"claims": {
"x-hasura-default-role": "admin",
"x-hasura-allowed-roles": [
"user",
"admin"
]
}
}
TOKEN from FireBase Auth
{
"role": "webuser",
"schema": "customer1",
"userid": "15",
"claims": {
"x-hasura-default-role": "user",
"x-hasura-allowed-roles": [
"user",
"admin"
],
"x-hasura-user-id": "OS2T2rdkM5UlhfWLHEjNExZ71lq1",
"x-hasura-dbuserid": "15"
},
"iss": "https://securetoken.google.com/postgrest-b4c8c",
"aud": "postgrest-b4c8c",
"auth_time": 1573155319,
"user_id": "OS2T2rdkM5UlhfWLHEjNExZ71lq1",
"sub": "OS2T2rdkM5UlhfWLHEjNExZ71lq1",
"iat": 1573164629,
"exp": 1573168229,
"email": "johnny1#gmail.com",
"email_verified": false,
"firebase": {
"identities": {
"email": [
"johnny1#gmail.com"
]
},
"sign_in_provider": "password"
}
}
How can I get this customToken to work with the existing JWT secret keys I have configured??
As documented in in Firebase Authentication: Users in Firebase Projects: Auth tokens, the tokens from Firebase Auth and the Admin SDK Custom Tokens are not the same, incompatible with each other and are verified differently.
Edited response after clarification:
As you are trying to identify the cloud functions instance as an authorative caller of your third-party API, you may use two approaches.
In both of the below methods, you would call your API using postToApi('/saveUserData', { ... }); in each example. You could probably also combine/support both server-side approaches.
Method 1: Use a public-private key pair
For this version, we use a JSON Web Token to certify that the call is coming from a Cloud Functions instance. In this form of the code, the 'private.key' file is deployed along with your function and it's public key kept on your third-party server. If you are calling your API very frequently, consider caching the 'private.key' file in memory rather than reading it each time.
If you ever wish to invalidate this key, you will have to redeploy all your functions that make use of it. Alternatively, you may modify the fileRead() call and store it in Firebase Storage (secure it - readable by none, writable by backend-admin). Which will allow you to refresh the private key periodically by simply replacing the file.
Pros: Only one remote request
Cons: Updating keys could be tricky
const jwt = require('jsonwebtoken');
const rp = require('request-promise-native');
const functionsAdminId = 'cloud-functions-admin';
function getFunctionsAuthToken(jwtOptions) {
jwtOptions = jwtOptions || {};
return new Promise((resolve, reject) => {
// 'private.key' is deployed with function
fs.readFile('private.key', 'utf8', (err, keyData) => {
if (err) { return reject({src: 'fs', err: err}); }
jwt.sign('cloud-functions-admin', keyData, jwtOptions, (err, token) => {
if (err) { return reject({src: 'jwt', err: err}); }
resolve(token);
});
});
});
}
Example Usage:
function postToApi(endpoint, body) {
return getFunctionsAuthToken()
.then((token) => {
return rp({
uri: `https://your-domain.here${endpoint}`,
method: 'POST',
headers: {
Authorization: 'Bearer ' + token
},
body: body,
json: true
});
});
}
If you are using express on your server, you can make use of express-jwt to deserialize the token. If configured correctly, req.user will be 'cloud-functions-admin' for requests from your Cloud Functions.
const jwt = require('express-jwt');
app.use(jwt({secret: publicKey});
Method 2: Add a cloud-functions-only user
An alternative is to avoid the public-private key by using Firebase Auth. This will have the tradeoff of potentally being slower.
Pros: No key management needed, easy to verify user on server
Cons: Slowed down by Firebase Auth calls (1-2)
const admin = require('firebase-admin');
const rp = require('request-promise-native');
const firebase = require('firebase');
const functionsAdminId = 'cloud-functions-admin';
function getFunctionsAuthToken() {
const fbAuth = firebase.auth();
if (fbAuth.currentUser && fbAuth.currentUser.uid == uid) {
// shortcut
return fbAuth.currentUser.getIdToken(true)
.catch((err) => {src: 'fb-token', err: err});
}
return admin.auth().createCustomToken(functionsAdminId)
.then(function(customToken) {
return fbAuth.signInWithCustomToken(token)
.then(() => {
return fbAuth.currentUser.getIdToken(false)
.catch((err) => {src: 'fb-token', err: err});
})
.catch((err) => {src: 'fb-login', err: err});
})
.catch((err) => {src: 'admin-newtoken', err: err});
}
Example Usage:
function postToApi(endpoint, body) {
return getFunctionsAuthToken()
.then((token) => {
return rp({
uri: `https://your-domain.here${endpoint}`,
method: 'POST',
headers: {
Authorization: 'Bearer ' + token
},
body: body,
json: true
});
});
}
On your server, you would use the following check:
// idToken comes from the received message
admin.auth().verifyIdToken(idToken)
.then(function(decodedToken) {
if (decodedToken.uid != 'cloud-functions-admin') {
throw 'not authorized';
}
}).catch(function(error) {
// Handle error
});
Or if using express, you could attach it to a middleware.
app.use(function handleFirebaseTokens(req, res, next) {
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
var token = req.headers.authorization.split(' ')[1];
admin.auth().verifyIdToken(idToken)
.then((decodedToken) => {
req.user = decodedToken;
next();
}, (err) => {
//ignore bad tokens?
next();
});
} else {
next();
}
});
// later on: req.user.uid === 'cloud-functions-admin'
Original response:
If your client uses Firebase Authentication from an SDK to log in and your server uses the Admin SDK, you can use the client's ID token on the cloud function to speak to your server to verify a user by essentially "passing the parcel".
Client side
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
// Send token to your cloud function
// ...
}).catch(function(error) {
// Handle error
});
Cloud Function
// idToken comes from the client app
admin.auth().verifyIdToken(idToken) // optional (best-practice to 'fail-fast')
.then(function(decodedToken) {
// do something before talking to your third-party API
// e.g. get data from database/secret keys/etc.
// Send original idToken to your third-party API with new request data
}).catch(function(error) {
// Handle error
});
Third-party API
// idToken comes from the client app
admin.auth().verifyIdToken(idToken)
.then(function(decodedToken) {
// do something with verified user
}).catch(function(error) {
// Handle error
});

Firebase Cloud Messaging click_action not working

I am sending notification from Firebase console to web application (Firebase). When I am sending a POST request from POSTMAN, I am able to navigate to the URL when I click on the notification. But when I am sending it from Firebase console I am not able to open the URL. Also, I need to add my logo as my Icon to the notification.
POSTMAN
{
"notification": {
"title": "Push notification test",
"body": "It works! 🎉",
"icon": "https://soft-ing.info/img/firebase.png",
"click_action": "https://google.com"
},
"to": "dLXCbmVCh5Y:APA91bGmFN7BUsKqwWFokyoBsoph6k4EhBQEflwJLFrPaUzTceYhAPYfFf8LqTRBVJGCA0gWS_0k0DUCeJBa7jdopIyjFQNprtp3lkQgLmUNRUibLIIMxAuBZeXuHTqaU-BA4QwbekN6"
}
Service Worker File Code
messaging.setBackgroundMessageHandler(function(payload) {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
// Customize notification here
const notificationTitle = payload.data.title;//'Background Message Title';
const notificationOptions = {
body: payload.data.body,//'Background Message body.',
icon: payload.data.icon,
image : payload.data.image,
data:{
time: new Date(Date.now()).toString(),
click_action : payload.data.click_action
}
};
return self.registration.showNotification(notificationTitle,notificationOptions);
});
self.addEventListener("notificationclick", (event) => {
event.waitUntil(async function () {
const allClients = await clients.matchAll({
includeUncontrolled: true
});
let chatClient;
let appUrl = 'https://www.google.com';
for (const client of allClients) {
//here appUrl is the application url, we are checking it application tab is open
if(client['url'].indexOf(appUrl) >= 0)
{
client.focus();
chatClient = client;
break;
}
}
if (!chatClient) {
chatClient = await clients.openWindow(appUrl);
}
}());
});
There's some discrepancy in the above two snippets you shared.
In your case body: payload.data.body should be body: payload.notification.body, you need to do similarly for other places in service worker since that's how you are sending the request.

Send push notifications using Firebase and OneSignal

Not sure if this is possible, but I have an existing Ionic 3 app which uses Firebase as a backend. Maybe it's just me, I'm not able to integrate Google login, Facebook login and push notifications in the same app. Been trying for a few days now.
I was able to install OneSignal and send push notifications to an Android device, but I want to send them programatically using tokens which are saved for each device, not from the OneSignal dashboard.
This is what I use in Firebase Cloud Functions to send notifications. Can it be modified to send the notification to OneSignal and then to each device?
`function sendFcm(userID, eventSnapshot, eventID) {
const getDeviceTokensPromise = admin.database().ref(`/fcmTokens/${userID}/`).once('value');
return Promise.all([getDeviceTokensPromise]).then(result => {
const tokensSnapshot = result[0];
const payload = {
"notification": {
"title": "Your invitation has arrived",
"body": eventSnapshot.name,
"sound": "default",
// "click_action": "FCM_PLUGIN_ACTIVITY",
"icon": "fcm_push_icon"
},
"data": {
"eventId": eventID,
"uid": userID,
"eventObj": JSON.stringify(eventSnapshot),
"notificationType": "newEventNotification"
}
};
const tokens = Object.keys(tokensSnapshot.val());
console.log(tokens);
// Send notifications to all tokens.
return admin.messaging().sendToDevice(tokens, payload).then(response => {
// For each message check if there was an error.
const tokensToRemove = [];
response.results.forEach((result, index) => {
console.log(tokens[index]);
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
// Cleanup the tokens which are not registered anymore.
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove());
}
}
});
return Promise.all(tokensToRemove);
});
})
}`
After searching a bit, I found the OneSignal API. Seems that I just need to save the player id and send it or mutiple in an array to onesignal.com/api/v1/notifications. More details here: https://documentation.onesignal.com/reference#section-send-based-on-onesignal-playerids-create-notification

Vue pwa with firebase cloud messaging not working properly

im trying the following code:
navigator.serviceWorker.register('service-worker.js')
.then((registration) => {
const messaging = firebase.messaging().useServiceworker(registration)
console.log(messaging)
messaging.requestPermission().then(function () {
console.log('Notification permission granted.')
messaging.getToken().then(function (currentToken) {
if (currentToken) {
console.log(currentToken)
}
})
})
})
my manifest:
{
"name": "Herot-Eyes",
"short_name": "herot-eyes",
"gcm_sender_id": "103953800507",
"icons": [
{
"src": "/static/img/icons/herot-eyes-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/static/img/icons/herot-eyes-512x512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "/static/img/icons/apple-touch-icon-180x180.png",
"sizes": "180x180",
"type": "image/png"
}
],
"start_url": "/",
"display": "fullscreen",
"orientation": "portrait",
"background_color": "#000000",
"theme_color": "#2196f3"
}
what is going wrong? my console.log(messaging) is returning a factory error, the following:
bad-push-set : "The FCM push set used for storage / lookup was not not
a valid push set string." bad-scope
"The service worker scope must be a string with at least one
character." bad-sender-id
"Please ensure that 'messagingSenderId' is set correctly in the
options passed into firebase.initializeApp()." bad-subscription
"The subscription must be a valid PushSubscription." bad-token : "The
FCM Token used for storage / lookup was not a valid token string."
bad-vapid-key
"The public VAPID key is not a Uint8Array with 65 bytes."
bg-handler-function-expected
"The input to setBackgroundMessageHandler() must be a function."
delete-scope-not-found
"The deletion attempt for service worker scope could not be performed
as the scope was not found." delete-token-not-found
"The deletion attempt for token could not be performed as the token
was not found." failed-delete-vapid-key
"The VAPID key could not be deleted."
failed-serviceworker-registration
"We are unable to register the default service worker.
{$browserErrorMessage}" failed-to-delete-token
"Unable to delete the currently saved token." get-subscription-failed
"There was an error when trying to get any existing Push
Subscriptions." incorrect-gcm-sender-id
"Please change your web app manifest's 'gcm_sender_id' value to
'103953800507' to use Firebase messaging." invalid-delete-token
"You must pass a valid token into deleteToken(), i.e. the token from
getToken()." invalid-public-vapid-key
"The public VAPID key must be a string." invalid-saved-token
"Unable to access details of the saved token."
no-fcm-token-for-resubscribe
"Could not find an FCM token and as a result, unable to resubscribe.
Will have to resubscribe the user on next visit." no-sw-in-reg
"Even though the service worker registration was successful, there was
a problem accessing the service worker itself."
no-window-client-to-msg
"An attempt was made to message a non-existant window client."
notifications-blocked
"Notifications have been blocked." only-available-in-sw
"This method is available in a service worker context."
only-available-in-window
"This method is available in a Window context." permission-blocked
"The required permissions were not granted and blocked instead."
permission-default
"The required permissions were not granted and dismissed instead."
public-vapid-key-decryption-failed
"The public VAPID key did not equal 65 bytes when decrypted."
should-be-overriden
"This method should be overriden by extended classes."
sw-reg-redundant
"The service worker being used for push was made redundant."
sw-registration-expected
"A service worker registration was the expected input."
token-subscribe-failed
"A problem occured while subscribing the user to FCM: {$message}"
token-subscribe-no-push-set
"FCM returned an invalid response when getting an FCM token."
token-subscribe-no-token
"FCM returned no token when subscribing the user to push."
token-unsubscribe-failed
"A problem occured while unsubscribing the user from FCM: {$message}"
token-update-failed
"A problem occured while updating the user from FCM: {$message}"
token-update-no-token
"FCM returned no token when updating the user to push."
unable-to-resubscribe
"There was an error while re-subscribing the FCM token for push
messaging. Will have to resubscribe the user on next visit.
{$message}" unsupported-browser
"This browser doesn't support the API's required to use the firebase
SDK." use-sw-before-get-token
"You must call useServiceWorker() before calling getToken() to ensure
your service worker is used."
Configure to server to receive notifications
Inside public folder, add the following line to manifest.json:
{
//...manifest properties
"gcm_sender_id": "103953800507" <--- add this line to the file
}
Note: if the project wasn't created using Vue Cli, manually create the manifest.json file. (Thanks #natghi)
firebase-messaging-sw.js
importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-messaging.js');
var config = {
messagingSenderId: <Sender ID>
};
firebase.initializeApp(config);
let messaging = firebase.messaging();
In your main.js file add the following code
var config = {
apiKey: <API_KEY>,
authDomain: <DOMAIN>,
databaseURL: <DATABASE_URL>,
projectId: <PROJECT_ID>,
storageBucket: <STORAGE_BUCKET>,
messagingSenderId: <SENDER_ID>
};
firebase.initializeApp(config);
Vue.prototype.$messaging = firebase.messaging()
navigator.serviceWorker.register('/firebase-messaging-sw.js')
.then((registration) => {
Vue.prototype.$messaging.useServiceWorker(registration)
}).catch(err => {
console.log(err)
})
Receive notifications
Then in your App.vue, add this code to the created() function
created() {
var config = {
apiKey: <API_KEY>,
authDomain: <DOMAIN>,
databaseURL: <DATABASE_URL>,
projectId: <PROJECT_ID>,
storageBucket: <STORAGE_BUCKET>,
messagingSenderId: <SENDER_ID>
};
firebase.initializeApp(config);
const messaging = firebase.messaging();
messaging
.requestPermission()
.then(() => firebase.messaging().getToken())
.then((token) => {
console.log(token) // Receiver Token to use in the notification
})
.catch(function(err) {
console.log("Unable to get permission to notify.", err);
});
messaging.onMessage(function(payload) {
console.log("Message received. ", payload);
// ...
});
}
Send notification
UPDATE
const admin = require("firebase-admin")
var serviceAccount = require("./certificate.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
const Messaging = admin.messaging()
var payload = {
webpush: {
notification: {
title: "Notification title",
body: "Notification info",
icon: 'http://i.imgur.com/image.png',
click_action: "http://yoursite.com/redirectPage"
},
},
topic: "Doente_" + patient.Username
};
return Messaging.send(payload)
Older version
Then, in postman you do the following request
POST /v1/projects/interact-f1032/messages:send HTTP/1.1
Host: fcm.googleapis.com
Authorization: Bearer <SENDER_TOKEN>
Content-Type: application/json
{
"message":{
"token" : The token that was in the console log,
"notification" : {
"body" : "This is an FCM notification message!",
"title" : "FCM Message"
}
}
}
Sender Token
In your backend, use the following code, where the file "certificate.json" was got in the firebase dashboard (https://firebase.google.com/docs/cloud-messaging/js/client - Generate pair of keys)
const {google} = require('googleapis');
function getAccessToken() {
return new Promise(function(resolve, reject) {
var key = require('./certificate.json');
var jwtClient = new google.auth.JWT(
key.client_email,
null,
key.private_key,
["https://www.googleapis.com/auth/firebase",
"https://www.googleapis.com/auth/cloud-platform"],
null
);
jwtClient.authorize(function(err, tokens) {
if (err) {
reject(err);
return;
}
resolve(tokens.access_token);
});
});
}
getAccessToken().then(senderToken => console.log(senderToken))
The senderToken is used on the Authorization header to send a notification

How do I set "collapse_key" in Firebase Admin.messaging() SDK?

admin.messaging().sendToDevice(tokens, payload)
Here's the payload:
const payload = {
collapse_key: "something",
notification: {
body: message.body || "[ Sent you a photo ]",
},
data:{
"thread_id": String(thread_id),
"message_id": String(message_id),
"user_id": String(message.user_id),
"created_at": String(message.created_at),
}
};
Error: Messaging payload contains an invalid "collapse_key" property. Valid properties are "data" and "notification".
Do I need to use REST API for this? If so, that's really bad, because I have to pay extra for outgoing requests...
collapseKey is a property of MessagingOptions .You pass the options as the third parameter of sendToDevice().
const options = {
priority: 'high',
collapseKey: 'myCollapseKey'
};
admin.messaging().sendToDevice(tokens, payload, options)
.then(function(response) {
console.log("Successfully sent message:", response);
})
.catch(function(error) {
console.log("Error sending message:", error);
});
A complete example is in the documentation.
For those looking for an updated solution using most recent firebase-admin SDK and the new send() method, here's how I built my notification:
{
"token": TARGET_DEVICE_FCM_TOKEN,
"notification": {
"title": "Notification title",
"body": "Optional body",
},
"android": {
"collapseKey": "YOUR_KEY",
}
...rest of payload
}
Source: Package types

Resources