Firebase cloud functions security - firebase

I'm using firebase cloud functions with the admin sdk in order to perform some operations on the realtime database on behalf of the user.
For example, if the user wants to store a new parameter on the database, it will press a button on its android client which relies on a directly callable function (onCall). This callable function will save the parameter in the right location in the database.
Now, I understand the onCall function receives automatically some info about the user in the context parameter, such as the user id and the token, but I don't know if these parameters are used to control the effective identity of the user.
Since the function is running with the admin sdk in full privilege mode, I want to be sure that the user is who he claims to be. In a scenario in which one user steals the uid of another user, and calls the function with this uid but with its own Auth token, he will be detected?
Does the function check if the user id (uid) is compliant with the Auth token?
If yes, the only thing I have to do is to check if context.auth is different from null?

Just to move from the comments, the context.auth.uid value is validated server side, so you can trust that the user hasn't manipulated the value. (See the code here.) Now, when you use the firestore or realtime db admin API, the function has access to any value in the database, so you do need to ensure the user has access to the value in the DB (i.e. add a where uid='uid').

Related

Synchronize users created with Firebase Auth to my custom backend

I want to use Firebase Auth for my user login/registration process. Everything else should be handled by my own backend (spring boot app + postgres db).
Now I'm asking myself how I can synchronize a new created user to my user table in postgres. I thought about the following:
REST call through client - Everytime I get a success event from the firebase sdk I call an additional request to my backend which sends uid, username etc.
Problem: What if my backend call fails but the register process was successful ? That would lead to an inconsistent state since (at least thats what I understanded) I can't easily rollback. That would lead to situations where a user can login into my app without my backend knowing the user. This would crash/ invalidate all my following queries (e.g. search after user xyz would lead to no result even though he/she exists)
Check the existence of the user in the postgres database
Here I would query the uid from the database (which I got from the jwt) and create a new user if it doesn't exists in every incoming request.
Problem: The user query is a unnessecary overhead for every incoming request.
Trigger with cloud functions - When I understood it right firebase auth is firing events when a new user is created in cloud functions. This could be used to make the external api call.
Problem: I dont know what happens when my external rest call fails at this point. Can I rollback the registration ? Will I be ever catch this event again ? I also proably would have an eventual consistency situation, since I dont know when the cloud function triggers. Furthermore I would prefer not to include cloud functions to my stack
Is there any way how I could do this in a transactional manner ? Did anyone else tried is using sth simular ?
Thanks for every help!
The easiest way is actually to not synchronize auth data, but instead decode and verify the ID token of the user in your backend code.
This operation is (by design) stateless, although Firebase's own backend services often implement a cache of recently decoded tokens to speed up future calls with the same ID token.
Apparently, I finally came up with a different solution:
Register user per Firebase SDK (e.g. with email + pw method)
Make a post-call to my own registration api including the resulting uid from the previous step and some metadata
API creates a new user including a column with the UID + Fetches the firebase token of the user and adds an internal claim that references to the internal Postgres UUID via Admin SDK.
Frontend gets the created user and hard refreshes (very important, since the previously fetched token won't contain the newly added claim !) the firebase token and verifies that it contains the token. If it does -> everything is cool, if not some oopsie happened :) That will require a request retry.
Later when you start your app you can just check if the passed token contains the custom claim, if not open the sign up/sign in page.
Every endpoint except the one for registration should check if the claim is set. If not just forbid the request.
How to set custom claims:
https://firebase.google.com/docs/auth/admin/custom-claims#set_and_validate_custom_user_claims_via_the_admin_sdk
You can use the Firebase Admin SDK to create the user account from your back-end instead of from the client.
So first you create the user in your database, then grab the ID and use it to create a user with the same ID in Firebase.
If all goes well, send a confirmation to the client and sign it in using the same credentials they entered.
Why not creating an endpoint in your backend service and call this endpoint when a client side authentication succeeds?
This method should do 2 things:
decode token to get access to Firebase user object (Firebase Admin)
Compare Firebase user with your internal user table. if it doesn't exist you can create it using firebase user object, otherwise do nothing.
This solution allows you to do other nice things as well (Syncing user info between Firebase and your internal db, providing a way to let a frontend know if this user is new or not, ...) at a relative small cost (1 get call per sign in)

Firebase signInWithCustomToken custom claims not working in emulator only

I am setting up a firebase authentication system with firebase. As I wanted it to be username and password, I needed to do it myself.
The workflow is this:
Cloud function gets username and password
It get the corresponding email from firestore
It use signInWithEmailAndPassword() to sign in and get the user data
It it createCustomToken() with the user uid and data that I need
It returns the token to the webclient
Webclient use signInWithCustomToken() with the token
The onAuthStateChanged callback gets trigger and setup my data from the user data
This process works great when deploy in firebase.
The problem is that when I want to use it with the firebase emulator, the user variable on step 7 has no data (no email, no displayName, etc are all set to null). I know that custom token are not signed when generated from the emulator, but is there a way to still get the data out of them?
Thanks

Can you trigger (firebase) cloud functions on auth login (but not account creation)?

It seems like the firebase auth triggers are onCreate and onDelete. I'd like to also fire some kind of trigger on non-create login (basically periodically syncing the user's avatar and display name and such with what's in the database).
I can fake this by just doing it on the client side, which is what i'm doing right now - just updating the user record with whatever is in firebase.auth.user. Excitingly, this can't be done right after login, since if the login is also account creation, the user record tends to not exist (since it is created via a triggered cloud function), and I can't tell from auth.signInWithPopup() if the resultant signin was a creation or login event.
There is no such trigger. As you've observed, it just provides onCreate and onDelete.
Firebase Auth doesn't provide a way to sync the user's avatar with their authentication provider. It just copies the URL once at the time the account was created.
If you need to update the user's profile picture, you will have to do that yourself by calling updateProfile() on the user object and provide a URL for the picture.

Flutter Firebase, logout other user than me

In my Flutter management application, I would like to disconnect a user other than the one I am logged in with.
I use Firebase as my database, and use email and a password to log in. I don't use a token
I know that in order to disconnect the user I am currently logged in with, it has to be done like this:
FirebaseAuth.instance.signOut();
I can't figure out how to disconnect another user.
In the Firebase client-side SDKs, you can only sign out the currently signed in user. There is no way to sign out a user on another device.
If you want to add user management functionality to your client-side application, you'll want to use a combination of the client-side SDK and a server-side Admin SDK to build this functionality:
Use the Admin SDK in a trusted environment (such as a server you control, or Cloud Functions) to implement the user management functionality.
Create an endpoint that clients can call that exposes this functionality, for example as a callable Cloud Function.
In that endpoint be sure to check whether the user is authorized, for example by checking their ID token to see if it has a certain UID or custom claim.
Call the custom endpoint from your client-side code.

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