How do I register new vapidKeys outside FCM console UI - javascript

Update
As Gerardo pointed out, vapidKey != FCM Token.
I finally got things going, though im unsure if this is the best way to do it.
This was what i needed to generate a NEW token:
try{
if(currentToken) await deleteToken(messaging,currentToken)
if(Notification.permission !== 'granted') await Notification.requestPermission();
currentToken = await getToken(messaging,vapidKey);
$.ajax({
type:"GET",
url:"/fcm/register-token",
data:{token:currentToken},
success:(saved)=>{
if(saved) console.log(saved,' New token registered ',currentToken)
else console.log('Unable to save token!')
}
})
}
catch(e){console.log(e)}
Question
I am testing FCM for push notifications in javascript. I have a single "Web Push Certificate" (vapidKey) generated manually in the FCM console UI. This works for sending a test notification to a single token, but i am unclear on how any additional users would be able to register a new key with FCM.
I must be misunderstanding something fundamental here, and am not even sure what to be searching for.
// TODO: Find out how to generate new registration token as needed
const vapidKey = {vapidKey:"BLgq_6FY0suqAy9llMyYX03o9K9n4FHba1gCnPz6KmG9oh6K8v3Ws4SIG9KTILfN0_8np9PfT5bPqgTDXfCA9dc"};
async function fcm_init(){
try{
// TODO: user interaction for notification permission
await Notification.requestPermission()
const currentToken = await getToken(messaging,vapidKey);
// TODO: Send token to server to save in tbl_fcm
console.log(currentToken);
}
catch(err){
console.log(err);
}
}fcm_init()
In this example, the vapidKey is hard-coded, but how would i go about getting a different key for each user in the app without manually doing so in the FCM Console UI?
This is where i have generated the key to get a token for a user. This is what im trying to avoid doing manually for each user on the system.

The vapidKey is needed for identifying the app (not the user). This is mentioned in the second screenshot "Firebase Cloud Messaging can use Application Identity key pairs to connect with external push services".
With this information, your app can generate an FCM Token (or registration token) in the client:
const currentToken = await getToken(messaging,vapidKey);
You can use these FCM tokens to send messages to a specific device. These FCM Tokens identify the device (user) and they will be different for each one.
Check this example on how you would send a message from the back end to a specific device using it's FCM token (registration token).

Related

How to validate JWT token from Google pub/sub push (No pem found for envelope)

Context
I'm following Google's RTDNs guide on enabling Real-Time Developer Notifications. I've successfully created the topic and subscription and have received the push notifications sent to the API that I have created. I would now like to authenticate and validate these messages. For that, I'm following this guide on Authentication and Authorization. Their developer documentation here and here has a seemingly useful example.
The Issue
After following the resources outlined above, I get the following error:
Error: No pem found for envelope: {"typ":"JWT","alg":"HS256"}
Relevant Code
const authClient = new OAuth2Client();
// ...
app.post('/pubsub/authenticated-push', jsonBodyParser, async (req, res) => {
// Verify that the push request originates from Cloud Pub/Sub.
try {
// Get the Cloud Pub/Sub-generated JWT in the "Authorization" header.
const bearer = req.header('Authorization');
const [, token] = bearer.match(/Bearer (.*)/);
// Verify and decode the JWT.
// Note: For high volume push requests, it would save some network
// overhead if you verify the tokens offline by decoding them using
// Google's Public Cert; caching already seen tokens works best when
// a large volume of messages have prompted a single push server to
// handle them, in which case they would all share the same token for
// a limited time window.
// verifyIdToken is failing here with the `No pem found for envelope` error
const ticket = await authClient.verifyIdToken({
idToken: token,
audience: 'example.com',
});
// ...
} catch (e) {
res.status(400).send('Invalid token');
return;
}
res.status(200).send();
});
The Questions
From this, I'm assuming I need to have some public key.
Where do I get said public key?
Where do I put said public key so that the google client is initialized with it?
How can I generate an example JWT to test my endpoint?
Edits
I was able to find the source of this error in their code here:
if (!Object.prototype.hasOwnProperty.call(certs, envelope.kid)) {
// If this is not present, then there's no reason to attempt verification
throw new Error('No pem found for envelope: ' + JSON.stringify(envelope));
}
However, I've verified that the kid attribute does indeed exist in the decoded object:
{"alg":"RS256","kid":"7d680d8c70d44e947133cbd499ebc1a61c3d5abc","typ":"JWT"}
Turns out the kid was invalid and therefore threw the No pem found for envelope error. Once a valid kid was supplied, the error no longer persisted.

How can access google calendar of user and edit it without asking for user permisssion again and again

On my website, I am asking for google calendar access. I can edit the user calendar but, I don't want to ask for user permission, again and again, so once the user authorized and give access to google calendar, I can edit it anytime until the user revokes the access. Should I implement it on the frontend or the backend and how? I checked few answers where they mention we can use a service account but, it is not clear how can I edit or read the individual user's calendar events and how can I remove it once the user revokes access. This question was deleted because code was missing so adding code below.
I tried this so once user login I get access token and I am using it
window.gapi.load("client:auth2", () => {
window.gapi.client.setApiKey("api_key");
window.gapi.client.load("https://content.googleapis.com/discovery/v1/apis/calendar/v3/rest")
.then(() => {
window.gapi.auth.setToken({ access_token: access_token })
window.gapi.client.calendar.events.insert({
"calendarId": "id",
'resource': event
}).then((res) => {
console.log("calendar data res "+JSON.stringify(res))
}).catch(err => console.log("error getting calendar data "+JSON.stringify(err)))
}).catch(err => console.error("Error loading GAPI client for API", err) )
})
but once access token expires how can I get a new access token( I don't want to show login popup to the user again and again. I want to know how can I do it using refresh token on client-side).
You can't get a refresh token on the client-side without exposing your secret key to the public.
You can create an endpoint that accepts oAuth code and return the token, save the refresh token for later. You set up a corn job that checks for expired token and refreshes them.
Every time the user accesses your app, you grab a fresh token from the server and proceed to work normally.
As per Google guidelines. You do POST to https://oauth2.googleapis.com/token. Assuming your server-side stack is in Node.js, you do something like this using an HTTP client like Axios:
const Axios = require('axios');
const Qs = require('querystring');
const GOOGLE_CLIENT_ID = 'abc';
const GOOGLE_CLIENT_SECRET = '123';
let refreshToken = getFromDataBase(); // should be stored in database
Axios.post('https://oauth2.googleapis.com/token', Qs.stringify({
client_id: GOOGLE_CLIENT_ID,
client_secret: GOOGLE_CLIENT_SECRET,
refresh_token: refreshToken,
grant_type: 'refresh_token'
}), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
}
})
.then(({ data }) => console.log(data.access_token)) // new token that expires in ~1 hour
.catch(console.log)
Firstly, do you (a) want to update the calendar when the user is not logged in, for example in response to an external event? ... OR ... do you (b) only want to update the calendar from within a browser session?
If (a), then you need to ask the user for offline access which will give you a Refresh Token , which you can securely store on a server and use whenever you need to. (Forget all about Service Accounts).
If (b), then you need the following pieces of information :-
When the access token expires, request access again, but add the flag prompt=none. This will give you a fresh Access Token without the user seeing any UX.
Do this in a hidden iframe so that it is happening in the background and is invisible to the user. Your iframe will therefore always have an up to date Access Token which it can share with your app via localStorage or postMessage.

How to logout all sessions once user deleted from auth firebase service?

I wrote the following code for Firebase funcations:
exports.deleteVolunteer = functions.firestore.document(`${COLLECTION_PREFIX}/users/{userPhoneNumber}`).onDelete(async (snap, context) => {
const userPhoneNumber = context.params.userPhoneNumber;
try {
const userRecord = await admin.auth().getUserByPhoneNumber(userPhoneNumber);
await admin.auth().deleteUser(userRecord.uid);
console.log('Successfully deleted user with phone number: ' + userPhoneNumber);
} catch (error) {
console.log('Failed to delete user with phone number: ' + userPhoneNumber + ' with error ' + error);
}
return null;
});
Basically, once it see some document is removed in the cloud database, it removes the user from the auth service. I would like to exit all sessions from all devices that this user is logged in. As you can see the user connects to the app with a phone number. How can I do it?
When a user signs in to Firebase Authentication they get back an ID token that is valid for one hour. Until that token expires, there is no way to revoke it - at least not without changing the key that is used to sign all tokens.
This means that there is no way for the server to terminate existing sessions instantly.
Instead the common way to instantly lock out users is:
Send a signal to the clients that they need to refresh the token, which will sign out those clients - and prevent them from signing in again. This of course won't stop a malicious user from trying to use the existing token, so...
Check server-side whether the user account was deactivated before performing a sensitive operation. You can do this against the Firebase Authentication Admin SDK, but more common is to store the UIDs of recently deactivated accounts in the database you use, and then check in security rules or code.
For an example of this see the documentation on checking for ID token revocation.

Testing (Firebase) FCM

In order to understand how to use Firebase Cloud Messaging, I am following this document:
https://firebase.google.com/docs/cloud-messaging/admin/send-messages?hl=en-us
Precisely I am looking at this section: Send to individual devices
I can see in the code that I need a registrationToken. My question is how do I concretely get one?
I first want to send a message to my own iPhone, laying on my desk and later to all iPhones of registered users.
When I work in IOS-Swift, you have to add this method in your AppDelegate.swift file:
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) {
print("Firebase registration token: \(fcmToken)")
let dataDict:[String: String] = ["token": fcmToken]
NotificationCenter.default.post(name: Notification.Name("FCMToken"), object: nil, userInfo: dataDict)
// TODO: If necessary send token to application server.
// Note: This callback is fired at each app startup and whenever a new token is generated.
}
If you need to access the token directly, use this:
InstanceID.instanceID().instanceID { (result, error) in
if let error = error {
print("Error fetching remote instange ID: \(error)")
} else if let result = result {
print("Remote instance ID token: \(result.token)")
self.instanceIDTokenMessage.text = "Remote InstanceID token: \(result.token)"
}
}
For more information visit:
https://firebase.google.com/docs/cloud-messaging/ios/client
When working with FCM notifications, the devices generate tokens, which are renewed every so often, so if you need to send a push to a device you must know your token, you must implement a class that inherits FirebaseMessagingService, and overwrite an onNewToken method, this method is called in the background every time the device token is updated.
/**
* Called if InstanceID token is updated. This may occur if the security of
* the previous token had been compromised. Note that this is called when the InstanceID token
* is initially generated so this is where you would retrieve the token.
*/
#Override
public void onNewToken(String token) {
Log.d(TAG, "Refreshed token: " + token);
// If you want to send messages to this application instance or
// manage this apps subscriptions on the server side, send the
// Instance ID token to your app server.
sendRegistrationToServer(token);
}
It is recommended that this token be sent to your server so that from there you can send the push to the devices with registered tokens. If you want to force a first token, you can use:
FirebaseInstanceId.getInstance().getInstanceId();

Send notification using Cloud Functions for Firebase to specific user

I'm using Cloud Functions for Firebase to send notifications to the user. I'm able to get the notification but the problem is that everyone is getting the notification, I'm trying to send the notification to a particular user. I'm saving user's device id in Firebase's database and then send that particular person the notification. Here is my code:
To save user's data, which is actually working fine:
DatabaseReference root = FirebaseDatabase.getInstance().getReference();
DatabaseReference groupsRef = root.child("users").child(Settings.Secure
.getString(ctx.getContentResolver(), Settings.Secure.ANDROID_ID));
groupsRef.child("isLogin").setValue(2);
In first activity subscribing to the topic:
FirebaseMessaging.getInstance().subscribeToTopic("android");
And finally javascript code(something I know very little about):
var functions = require('firebase-functions');
var admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.database.ref('/users')
.onWrite(event => {
var eventSnapshot = event.data;
var str = "This is notification"
console.log(str);
var topic = "android";
var payload = {
data: {
isLogin: eventSnapshot.child("975af90b767584c5").child("isLogin").val()
}
};
return admin.messaging().sendToTopic(topic, payload)
.then(function (response) {
// See the MessagingTopicResponse reference documentation for the
// contents of response.
console.log("Successfully sent message:", response);
})
.catch(function (error) {
console.log("Error sending message:", error);
});
});
Here instead of "975af90b767584c5" which is hardcoded right now, I want to send device id, which I don't know how to do in javascript. Or if there is any other way.
Any help is appreciated, thanks.
First in app, get User FCM token by
String Token = FirebaseInstanceId.getInstance().getToken();
Now, send this token to your server and save it with other user details in Database.
Then when you want to send Notification to specific use, fetch that user's FCM token from Database via user id or something else.If you want to send notification to multiple user then fetch multiple user FCM token from database and put it in arrayList.
Now for main part, call fcm endpoint with your KEY, notification content
and most important: token or token array.
In your case, do not use sendToTopic, use send to: Token/Array
You can google for java script syntax, but this is main logic.For more info:
https://firebase.google.com/docs/cloud-messaging/admin/send-messages

Categories

Resources