how t check if notifications are allowed with FCM without triggering a permission prompt - firebase

I want to know if the user has allowed notifications on the current browser so I can show that state on the UI, but I don't want to trigger the request permission unless the user performs an action to enable notifications.
This is kind of a chicken egg problem because I don't have a reliable way to identify the current user browser.
I can store the FCM token on the firebase database and fetch it on the FE. But each token is unique for each browser and I can not compare it to anything, the only way I have to check if one of the existing tokens is for the current browser is to use the getToken method from firebase FCM and check the returned token is on the list of tokens, but that may trigger a request permission prompt and I don't want that unless the user asks for it.
One possible solution may involve storing a flag on local storage, but if the user clears it the UI will show notifications as disabled when the user is still subscribed to the FCM topic. I know the Notification API has a property that indicates if the user has granted notification permissions but that may have happened and the token not retrieved for some random network error, so I can not rely on this solely. Also it only indicates if the user has granted permissions, but on my application logic notifications may be disabled (because user choice) and that does not revokes the permissions.
Things are also not nice if user wants to disable notifications: in order to delete the token from the database, I need the current browser token, and the only reliable way to know the current browser token is to ask it with getToken which may trigger a request permission if the user has disabled notifications and I will never get the actual token and I need it to remove it from the database
Are there any guidelines about how to deal with this? The FCM docs are quite sparse

This is a solution that I'm not sure if it is super robust, but it's simple and probably to meet most cases.
First, check if the current browser has notifications allowed. If not, just show that value on the UI because the user will not get the notifications anyway even if they are subscribed on the backend side.
If notifications are already allowed you can safely run getToken (will not trigger permission request) and compare the value against the values on the user/profile database.
Because I'm using react here is an implementation on a hook:
const areNotificationsAllowed =
Notification && Notification.permission === "granted";
useEffect(() => {
// because we don't want to trigger a permission request
if (!areNotificationsAllowed) return;
// Notifications are allowed, we can safely run getToken to see if they are activated on this browser
messaging.getToken({ vapidKey }).then((token?: string) => {
if (!token) return setNotificationsEnabled(false);
if (notificationsCfg[token]) setNotificationsEnabled(true);
});
}, [areNotificationsAllowed])

Related

FCM Token - When should I store/save it on my DB?

I am not sure what a proper FCM token handling mechanism would be so I’m writing our process down here just to get some validation or suggestions for improvements:
Fetch FCM token on client Login (Flutter)
Save FCM token on our Database (Using our REST API)
Delete FCM token on Logout (Using our REST API)
Q1: Should we be getting the FCM token more often than just on login? AFAIK, FCM token only changes on app re-installs, clearing cache, etc. Does this also include app-updates from the PlayStore? In that case, should we save the FCM token on every app launch since the user will remain logged in after an app update and hence we wouldn't trigger the save FCM call.
Q2: Did I mention the right way to handle deleting FCM tokens from our DB? We don’t want the user to keep getting notifications once they have logged out.
Q3: An add-on idea is to send the device_id to the server along with the fcm_token so that server deletes all previously saved FCM tokens for that device_id. This is useful to not have useless tokens on the DB from cases where the user uninstalls the app without logging out (which means that the DELETE fcm_token call never went through.)
The FCM token is refreshed under conditions that you don't control, and those conditions have even changed over time. To handle token updates properly, you'll need to implement both initially getting the token and then monitoring for token updates.
Note that FCM tokens are not associated with a user. It is fine if you want to associate them with a user, but it's up to your application code in that case to maintain the association. So that for example includes deleting the token from your database when the user signs out, as you're doing in step 3. 👍
For keeping your token registry clean, you can indeed do this proactively as you intend, or reactively as shown here: https://github.com/firebase/functions-samples/blob/master/fcm-notifications/functions/index.js#L76-L88
Hi Rohan fundamentaly you should use below logic to save tokens on server.
Step1:
as soon as you get token in callback whether new or same try to save it localstorage.
Step2:
Call your REST API to save it to your server. it is upto you if you want to send unique user identifier along with the token.
Step3:
It is obvious you will recieve token callback a lot of time so you can check whether you have similar token in localstorage, it means you have the token on the server so no point calling REST API.
Step 4: Now your app can send events back to server and based on it trigger Push notifications to the users.
Step 5: You can Add/update user token based on uniqye user identifier. In some cases a user can be guest user, so your app should generate guest userId and link it with token.
Stay safe.

How to disallow disposable email in firebase auth email provider signup?

We are using firebase auth and firebase auth UI to authenticate a user.
We want to disallow or block those user's, who is trying to use a temporary mailbox for signing up (eg: https://www.mailinator.com).
There are 2 proposed solutions:
Do a client-side validation for the email ID? (Difficult to manage a blacklist of email providers in the client. Also, people can still use API to hack it).
After the user signs up, on onCreate user event, we can trigger a firebase function to validate the email ID against the blacklist, then we can disable or revoke the account. But here,
if we are disabling the user, he/she will get access to our app for next 1 hour as the client already gained the ID token.
If we are revoking refresh token, we'll have to again wait for 1 hour or write the rule to make a query to Firestore to check if user access is revoked. (Better if we can avoid this query)
Is there a better or native way to solve this issue?
If we are not getting any other solutions, we'll choose to go with 2nd option (revoking refresh token).
I would do a combination of both solutions you proposed. Doing the client side check will trip up most people and for the more tech savvy that try to get around it, your onCreate trigger will deal with them.
You could also add their uid to a blocked list in the realtime database from your onCreate trigger.
Then you can listen to it on the client and log them out. And for database rules you can check if they are in the block list and so block the read/write rule.
a regularly updated service to check DPA is already being maintained.
Do a simple get request to:
https://open.kickbox.com/v1/disposable/{user_email}
this would return the response
{
"disposable": true
}
if email id is disposable.
you can send the get request with complete email id or just the domain.
e.g. https://open.kickbox.com/v1/disposable/jamond67#zdecaesgl.com
or https://open.kickbox.com/v1/disposable/zdecaesgl.com

How to logout the user using Firebase Admin SDK?

So, I have created a cloud function using Firebase Admin SDK. The purpose of that function is to disable the user and after successfully disabling it, I want that user to be logged out from my application. I have disabled user but can't figure out how to log out the user.
I was wondering if there is any function of a workaround to achieve this?
A user that is signed in to your app has a ID token that is valid for up to an hour. Once that token has been created, there is no way to revoke it.
The typical way to handle your use-case is to also flag the user in a server-side database once you disable their account, and then check that flag in any operations.
For example, if your using the Firebase Realtime Database, and disable the user with Node.js, the code to also flag the user in the database could look like this:
// Disable the user in Firebase Authentication to prevent them from signing in or refreshing their token
admin.auth().updateUser(uid, {
disabled: true
}).then(function() {
// Flag the user as disabled in the database, so that we can prevent their reads/writes
firebase.database().ref("blacklist").child(uid).set(true);
});
And you can then check this in the server-side security rules with something like this:
{
"rules": {
".read": "auth.uid !== null && !root.child('blacklist').child(auth.uid).exists()"
}
}
This rule allows all users that are signed in (auth.uid !== null) full read access to the database, but blocks users who you've flagged (!root.child('blacklist').child(auth.uid).exists()).
For an (even) more elaborate example of this approach, see the documentation on session management.
There are two main types of tokens used in Firebase Auth that are relevant to your question here:
Refresh token
ID token (aka, access token)
Firebase ID tokens are short lived and last for an hour; the refresh token can be used to retrieve new ID tokens. Refresh tokens expire only when one of the following occurs:
The user is deleted
The user is disabled
A major account change is detected for the user. This includes events like password or email address updates.
https://firebase.google.com/docs/auth/admin/manage-sessions
So in your case, when you disable the user, the refresh token will be automatically revoked. This means that once the short-lived ID token expires, they won't be able to retrieve a new one.
But you want them to be logged out immediately after being disabled. There are two main considerations here:
if you control the well-behaved client application, you can voluntarily log them out in the client
if you want to truly protect against malicious actors, you can revoke the ID token on the backend
Voluntarily logging out in a well-behaved client
If the token is revoked via the Admin SDK, the client is informed of the revocation and the user is expected to reauthenticate or is signed out:
https://firebase.google.com/docs/auth/admin/manage-sessions#respond_to_token_revocation_on_the_client
However, the docs are very misleading here. There is no built-in behaviour to automatically inform the client of a revocation. Instead, you can follow the suggestions in this thread (https://groups.google.com/g/firebase-talk/c/cJjo9oknG6g/m/XG24x8SqEgAJ) which talk about how to implement this behaviour. The two main options presented are:
Use Firebase Realtime Database to build your own real-time "push" mechanism to detect revocations
Use currentUser.getIdToken(true) to force-fetch a new id token, which will detect the refresh token revocation, and log the user out (you should get an even on the onAuthStateChanged listener).
For option 2, note the parameter true passed in to forceRefresh. This is generally not a good option - you don't want to force refresh on every API request, but if you don't, it's hard to know when to do a force refresh.
When you refresh the page, the Firebase client SDK will typically automatically perform a force refresh.
Server-side detection
When a user's ID token is to be verified, the additional checkRevoked boolean flag has to be passed to verifyIdToken. If the user's token is revoked, the user should be signed out on the client or asked to reauthenticate using reauthentication APIs provided by the Firebase Authentication client SDKs.
https://firebase.google.com/docs/auth/admin/manage-sessions#detect_id_token_revocation_in_the_sdk
Note that using the checkRevoked=true option results in a network request from your backend to Firebase's backend, which is expensive. Again, it's hard to know when it's worth using checkRevoked. Perhaps it's worth the cost to always perform the network check on a small subset of highly sensitive APIs.
Summary
You should read through the docs in full (https://firebase.google.com/docs/auth/admin/manage-sessions) and see which approach suits you best.
Frank van Puffelen has already covered the other standard option - using rules to guard Firebase backend services.
But in general, there isn't anything that helps out of the box. If you understand the concept behind refresh tokens and id tokens, you'll notice that it's fundamentally not possible to revoke the ID token while retaining the performance benefits (ie, reducing network traffic) that is the entire reason for using the refresh+id model to begin with.
I'd just let the token expire, and accept that any "disable" can be delayed by up to 1 hour.

Deleting user from Admin API does not trigger .onAuthStateChanged in client

I am deleting a user from a firebase cloud function.
admin.auth().deleteUser(user_uid)
However, I was expecting that the following client side listener would trigger:
firebase.auth().onAuthStateChanged(() => // do stuff)
That is to say, the users deletion would also change his/her auth state from signedIn to signedOut.
The cloud-function call executes correctly, however the user is still logged in. Do I also have to log them out? (I see no admin .logOut method available.
I was thinking that after the successful Function execution I would then programmatically log them out from the client side.
Am I missing something?
EDIT: Deleting the user from the client side does trigger .onAuthStateChanged, so from the client side it does change his/ her auth state.
That's not the way auth state listeners work on the client. Those listeners trigger when the state changes from the perspective of the client. It doesn't know of changes that happen immediately on the server, at least not until the client tries to refresh its auth token. It's not "realtime" in connection with the server like Realtime Database.
If you want to wire up your own client notifications for user deletion, you could have the client listen to a per-user location in Realtime Database, and use the changes there to know when the user has been deleted.

Keeping emails synchronized between Firebase auth and database

I am using Firebase Web for a SaaS solution. My purpose is to have access to users' email at any time, either for sending notifications or triggering alerts from the backend.
For security reasons, Firebase auth does not allow to list users' email or to fetch emails based on user IDs. As a consequence, I keep a copy of the email into a specific collection in the Firebase database when a user account is created. The copy is made by a Cloud function that is triggered on user creation (following guidelines: https://firebase.google.com/docs/auth/extend-with-functions).
Thanks to the copy available in the Firebase database, I can access users' email. However, my issue is when a user changes his email.
Firebase auth provides the updateEmail function that returns a promise. I use this last to update the email in Firebase auth. Then, when the promise resolves I update the user email in the Firebase database. However, this has a major drawback: all the logic is performed on the client side and since both operations are not performed in a transaction if the client refreshes or closes his browser (or assume it crashes), then it is possible that Firebase auth is updated but not the Firebase database, thus leading to an inconsistent state.
I looked at the documentation, expecting the possibility to trigger a Cloud function when user auth information is updated. Unfortunately, I cannot find such a feature.
Another solution I thought about is to update the database from the Web client. Then, this last triggers a Cloud function that updates Firebase auth with the admin SDK. This last solution works but bypasses the check performed by updateEmail that ensures the new email is not used by another account. Also, the account hijacking protection performed by updateEmail is evicted, which is really bad from a security point of view.
Any idea to solve this problem properly is welcome.
Here are a couple of options:
When calling updateEmail, update the email in your database first before calling updateEmail. THowever, if an error occurs, you need to catch it and undo that change in your db.
When a user wants to updateEmail, send their id token and new email to your server or firebase function http endpoint. There you verify the ID token with the admin SDK, then use the client SDK require('firebase'), using the uid from the ID token, admin.auth().createCustomToken(uid), then using client SDK, firebase.auth().signInWithCustomToken(customToken). You can then call user.updateEmail(newEmail) on the backend and save the email.
Always save the uid only and just use Admin SDK admin.auth().getUser(uid) to look up the user and get their email. This guarantees you get the user's latest email as you will not be able to catch the email revocation if the user chooses to do so.
No need to save anything. Use the CLI SDK to download all your users and their emails. Check https://firebase.google.com/docs/cli/auth#authexport
This is also better as you will always be able to get the latest email per user even if they revoke the email change.

Resources