Directly validate Firebase token from react native app - firebase

I implemented Auth with Firebase + Facebook. To be able to use the Auth with my custom backend I implemented the admin SDK on my node Server. Now I am trying to validate if my Firebase token is still valid to know when I need to show the login screen. I could probably send the token to my custom backend. But since I don't need anything else from the backend, it would be easier to directly call firebase from the client.
Does someone know if a function like that exists?

Firebase client SDKs periodically refresh the ID tokens and keep them alive. Therefore the token will remain valid, until the client explicitly invokes a sign out operation. See this discussion for more details: https://groups.google.com/forum/#!msg/firebase-talk/rjR0zYiiEhM/Clt9aFtgAwAJ (It is a little old, but still relevant

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 Auth Token Persistence Security Risks

Current Setup
I'm using cloud functions and the admin sdk for my backend. The front-end is a combination of React, Firebase Auth (web sdk), and redux firebase for authentication. Currently, I force a token refresh for each request for specific components of my react app and save the token to local storage.
Reasoning
Initially, I thought I could simply listen for the auth state to change in my front-end's index.js and then refresh the token accordingly i.e. firebase.auth().onAuthStateChanged A few processes on my app can take quite a while (i.e. uploading content) and I've noticed the change of auth state doesn't get triggered anytime outside of the initial login or a logout.
For example, the user begins to upload content 1 minute before the token is set to expire. The final API requests following the uploading then fail because they finish after the token has expired. The token doesn't get updated and requests fail to my backend as 403 unauthorized. The solution was to use currentUser.getIdToken(true) upon the first load of each of these components. Therefore ensuring a new token is always available.
Problem
Are there security risks to this approach? If yes, how can I mitigate them or is there a better approach to persist the token? I'm nervous about giving users the ability to indefinitely refresh their tokens.
is there a better approach to persist the token?
You're supposed to use onIdTokenChanged to listen to changes in the user's ID token as it changes over time. It works like onAuthStateChanged, except you get fed ID tokens. It's updated automatically as needed by the client SDK. You can store the latest token for use in your calls without having to fetch one with an API call each time.
Are there security risks to this approach?
Not really. Refreshing an ID token is necessary, and the Auth SDK does it automatically internally anyway.

Trouble using Firebase Auth to access google API via gapi SDK

In my firebase (Angular) app, I'm using firebase authentication to log a user in via their Google Profile. As part of this process, the user gives me permission to access their gmail account (scope 'https://www.googleapis.com/auth/gmail.compose').
After the user has logged in this way, I want to configure the "gapi" google javascript SDK so that the "signed in user" is the user signed in via firebase auth. Here's where I'm having trouble.
It appears that I need to set the client token for the gapi sdk like so gapi.client.setToken(userAccessToken) and the token needs to be set before the gapi client is initialized. Attempting to do this doesn't seem to work however (a call to gapi.auth2.getAuthInstance().isSignedIn.get() returns false when it should return true).
I also can't figure out a way of changing the "signed in user" if the firebase user logs out and a new one logs in. This is because, again, the gapi client seems to require the gapi.client.setToken() be called before the client is initialized, and I can't see any way of re-initializing and already initialized gapi client.
I can get the gapi client working if I use the gapi client's own gapi.auth2.getAuthInstance().signIn() method, but then the user is asked to sign in to my app twice using (from the user's perspective) identical google login popup boxes (one prompt originating from firebase auth and the other from the gapi client).
Does anyone have any suggestions / tips? After someone logs in via firebase auth I can get access to their userAccessToken, I just can't figure out how to programmatically pass that to the gapi client in a clean way.
Ideally:
on application load the gapi client would also load and initialize.
When a user chose to sign in, I would be able to use Firebase Auth to log someone in via their google profile, then get their access token and pass it to the gapi client to make google api calls.
If the firebase user ever logged out, I would clear the gapi client's api token.
If a new firebase user logged in, I would re-set the gapi client's api token.
I have come upon a placeholder (i.e. non-ideal) solution to this problem by following this S.O. answer.
In short, the GAPI client does not seem to let you manually pass it an access token, but the firebase auth client does let you manually pass it an access token. So, instead of handling authentication with the firebase sdk and passing the token to the GAPI client, you need to do the reverse and handle authentication with the GAPI client and then pass the token to the firebase SDK.

Firebase Auth on Web - How to add spam protection for email/password login

For my website, I want to build my own login form for email/password based authentication using Firebase authentication instead of using FirebaseUI Web. I'll be using createUserWithEmailAndPassword JS function to create new user accounts. But how can I prevent spam registrations? Usually for web based forms, I would use Google Recaptcha and validate the recaptcha on my server. But here, I'm not using my server for creating the user accounts. I'm making a call on the client side to create the user accounts.
Of course, I'll be using email verification in the flow, but how would I prevent bots from creating the accounts in the first place?
I also understand that Firebase has some sort of limit for the number of requests per min from a single IP, but I would like to go further and try to prevent those registrations.
firebase.auth().createUserWithEmailAndPassword(email, password).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// ...
});
Thanks.
After 2 years, this question is still valid and as far as I see, it is not possible. You probably do not need an answer to this question anymore but it may help others. Even if you succeed in doing something manually, those js functions will stay there and can be called manually by any user who knows how to do it.
If there are no hidden, top secret options which are not available in the documentation, this is not possible. There is a recaptcha option but it is only for Phone Authentication. So, it seems like you have 2 options.
Ignore and delete users who do not verify their email address.
Disable email option from Firebase console and implement your own
email authentication. Generate your own token and log user into
Firebase with that custom token. https://firebase.google.com/docs/auth/web/custom-auth
I'm following up on frankish's answer. He is totally correct, and I agree I think it's strange that Firebase automatically integrates ReCaptcha when doing phone authentication (and now when doing Phone MFA), but does not provide support in createUserWithEmailAndPassword for passing a recaptcha verifier. Thus, the only way to really get around this is to do something like the following:
Set up ReCaptcha (either V2 or V3) manually on your signup page. Do NOT use firebase. auth. RecaptchaVerifier, that is only for integration with phone authentication.
Immediately after calling createUserWithEmailAndPassword, you need to make a call to your own server that passes up the recaptcha token. There is a Firebase blog post here about how to do that with a Firebase Function: https://firebase.googleblog.com/2017/08/guard-your-web-content-from-abuse-with.html. Note I think it's a bit strange that Firebase documented how to do this with server-side functions but didn't directly integrate this with account creation.
The final point is that in your server-side code, after you make the call to validate the recaptcha token, you need to set a custom claim on the Firebase user with the Firebase Admin API. That claim can be something like recaptchaPassed: true (or false). For details on custom claims see https://firebase.google.com/docs/auth/admin/custom-claims.
After that, you can then do things based on the value of that custom claim. For example you could read that custom claim in other server-side calls, or you can use it in Firestore security rules (good blog post on this, https://medium.com/google-developers/controlling-data-access-using-firebase-auth-custom-claims-88b3c2c9352a). You could also choose to immediately delete the user server-side (using the admin API), if recaptcha verification fails.
Note it's important to understand that there is nothing that guarantees that some malicious script will call your server-side token verification function after the code on the client calls createUserWithEmailAndPassword. Thus, the only way the rest of your code can guarantee that a particular Firebase user passed recaptcha verification is by looking for your custom claim that you set on the user server-side.

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