Firebase Rules Doesn't Let Algolia Run Node.js Searches Unless Rules Are Set to True

I installed Algolia and ran into some updating hiccups but they were resolved with the help of @dzello. He basically said when I set up my Node.js file I needed to listen to updates as opposed to setting up a one time only index. Everything worked exactly how he suggested.

What I didn’t realize was before I connected my Firebase Database to Algolia via my Node.js file my Firebase security rules were set to:

"read": true,
"write" : true

Which let Algolia index with no problems. However Firebase states that those rules are unsafe (anyone has access to your db) and should only be used for testing and not for live production. At a minimum they suggested to use:

"read": "auth != null",
"write": "auth != null"

These minimum rules verify that the user has been authenticated. The problem is once I set my rules to these minimums I can no longer run Algolia index searches on my FB Database.

As I was inside the app I noticed whenever I updated an entry, then went to search to look for it nothing appeared. I then went to terminal, cd to my Algolia folder and typed in npm start, and nothing would happen. Whatever trivial print statements were inside the Node.js file would run but none of the search functions or the print statements inside those search functions would fire.

Once I changed my security rules back to read/write: true, then if I ran npm start, all of the search functions and their print statements would fire.

It seems to me that Algolia isn’t being authenticated inside Firebase. I definitely added all the correct credentials otherwise I wouldn’t be able to index anything wether using true or not.

What should I do to let let Algolia authenticate inside Firebase?

Here is a similar question on Stackoverflow:

https://stackoverflow.com/questions/42868145/node-js-function-not-running

Here’s my Node.js code:

console.log('>>>THIS IS PRINTING<<<');

console.log(' ');

//Firebase Initialization
console.log('>>>We are here 1.<<<')
var admin = require("firebase-admin");
admin.initializeApp({
  credential: admin.credential.cert({
    projectId: "********",
    clientEmail: "********",
    privateKey: "-----BEGIN PRIVATE KEY-----\n********\n-----END PRIVATE KEY-----\n"
  }),
  databaseURL: "https://********.firebaseio.com/"
});

console.log('>>>We are here 2.<<<')
var firebase = require('firebase');
var config = {
    apiKey: "********",
    authDomain: "********.firebaseapp.com",
    databaseURL: "https://********.firebaseio.com/",
    storageBucket: "********.appspot.com",
    messagingSenderId: "********"
 };
 firebase.initializeApp(config);

console.log('>>>We are here 3.<<<')
//Algolia Initialization
var algoliasearch = require('algoliasearch');

var client = algoliasearch('********', '********');
var index = client.initIndex('sneakers');

var rootRef = firebase.database().ref('sneakers');

console.log('>>>We are here 12.<<<')
rootRef.on('value', reindexIndex);
function reindexIndex(dataSnapshot) {
  console.log('>>>We are here 14.<<<')
  // Array of objects to index
  var objectsToIndex = [];
  // Create a temp index
  var tempIndexName = 'sneakersTemp';
  var tempIndex = client.initIndex(tempIndexName);
  // Get all objects
  var values = dataSnapshot.val();
  // Process each Firebase object
  console.log('>>>0<<<');
  for (var key in values) {
    console.log('>>>We are here 15.<<<')
    if (values.hasOwnProperty(key)) {
      // Get current Firebase object
      console.log('>>>1<<<');
      var firebaseObject = values[key];
      // Specify Algolia's objectID using the Firebase object key
      firebaseObject.objectID = key;
      // Add object for indexing
      console.log('>>>2<<<');
      objectsToIndex.push(firebaseObject);
      console.log('>>>3<<<');
    }
  }
  console.log('>>>We are here 16.<<<')
  // Add or update new objects
  index.saveObjects(objectsToIndex, function(err, content) {
    if (err) {
      console.log('>>>We are here 17.<<<')
      throw err;
    }
    // Overwrite main index with temp index
    client.moveIndex(tempIndexName, 'sneakers', function(err, content) {
      if (err) {
        console.log('>>>We are here 18.<<<')
        throw err;
      }
      console.log('>>>We are here 19.<<<')
      console.log('Firebase<>Algolia reimport done');
      console.log('>>>4<<<');
    });
  });
}

// Listen for changes to Firebase data
console.log('>>>We are here 4.<<<')
rootRef.on('child_added', addOrUpdateObject);
rootRef.on('child_changed', addOrUpdateObject);
function addOrUpdateObject(dataSnapshot) {
  console.log('>>>We are here 5.<<<')
  // Get Firebase object
  var firebaseObject = dataSnapshot.val();
  // Specify Algolia's objectID using the Firebase object key
  firebaseObject.objectID = dataSnapshot.key;
  // Add or update object
  index.saveObject(firebaseObject, function(err, content) {
    if (err) {
      console.log('>>>We are here 6.<<<')
      throw err;
    }
    console.log('>>>We are here 7.<<<')
    console.log('Firebase<>Algolia object saved');
  });
}

console.log('>>>We are here 8.<<<')
// Listen for changes to Firebase data
rootRef.on('child_removed', removeIndex);
function removeIndex(dataSnapshot) {
  console.log('>>>We are here 9.<<<')
  // Get Algolia's objectID from the Firebase object key
  var objectID = dataSnapshot.key;
  // Remove the object from Algolia
  index.deleteObject(objectID, function(err, content) {
    if (err) {
      console.log('>>>We are here 10.<<<')
      throw err;
    }
    console.log('>>>We are here 11.<<<')
    console.log('Firebase<>Algolia object deleted');
  });
}

Here’s my package.json code:

{
  "name": "algolia_folder",
  "version": "1.0.0",
  "description": "Algolia Config Folder",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node app.js"
  },
  "author": "Lance Samaria",
  "license": "ISC",
  "engines": {
    "node": "7.8.0"
  },
  "dependencies": {
    "algoliasearch": "^3.24.5",
    "firebase": "^4.6.0",
    "firebase-admin": "^5.4.3"
  }
}

my Profile file:

worker: node ./app.js

Firebase rules set to true and npm start results:

true

true_allowed

Firebase rules set to auth != null and npm start results:

auth

auth_not_allowed

2 Likes

Hello,

I’ve been using Firebase myself recently and ran into similar issues. Your problem here is not related to Algolia, but more to Firebase. When you define your Firebase rules with auth != null, you’re basically telling Firebase that you have to be authenticated to read or write any data.

Being authenticated with Firebase means that you have to somehow tell Firebase what your identity is (through a login/password for example). Providing the Firebase api key in the initializeApp call is not enough (it took me some time to actually understand that). You actually have to call firebase.auth().{yourAuthMethodHere()}.

The Firebase documentation will give you an overview of all the available auth methods.

In my case (that is pretty similar to yours), I decided to authenticate using a custom token. The gist of it was to actually call firebase.auth().authenticateWithCustomToken(token) after calling firebase.initializeApp(). Be careful, this is an asynchronous call so you’ll have to wait for it to complete.

Now the question is “how do I get the token?”. You can mint (create) your own token using firebaseAdmin.auth().createCustomToken().

Basically the idea is to create a custom token with firebaseAdmin, then authenticate firebase with this token so it can correctly query the database.

Hope it will put you on the right track, and you might find more relevant answers on the Firebase forums directly :slight_smile:

@pixelplas hey, thanks for the help! I’m surprised no one else has brought this issue up? I’m not a Node dev, I only learned enough to get my IOS, Firebase, and Algolia in sync.

If I understand your suggestions clearly, this is a 2 step process. inside my app.js file, After the `Firebase-admin:

1st I get the custom token when I initialize firebaseAdmin

var myCustomToken;

var firebaseAdmin = require("firebase-admin");
firebaseAdmin.initializeApp({
  credential: admin.credential.cert({
    projectId: "********",
    clientEmail: "********",
    privateKey: "-----BEGIN PRIVATE KEY-----\n********\n-----END PRIVATE KEY-----\n"
  }),
  databaseURL: "https://********.firebaseio.com/"

 myCustomToken = firebaseAdmin.auth().createCustomToken()
});

2nd I pass the custom token after I initialize my firebase:

var firebase = require('firebase');
var config = {
    apiKey: "********",
    authDomain: "********.firebaseapp.com",
    databaseURL: "https://********.firebaseio.com/",
    storageBucket: "********.appspot.com",
    messagingSenderId: "********"
 };
 firebase.initializeApp(config);
 firebase.auth().authenticateWithCustomToken(myCustomToken)

If it’s asynchronous, in Swift there is a completion function that’s usually provided (or I can create my own), and after the first part is done the 2nd part gets triggered. In this case the 2nd part would be firebase.auth().authenticateWithCustomToken(myCustomToken)

So I have a few questions:

  1. Is my assertion correct?

  2. Assuming it is correct, I got the majority (99% of it) of the code inside my app.js file from your older documentation. Your updated docs (https://www.algolia.com/doc/tutorials/firebase-algolia) doesn’t say anything about installing the firebase dependency

    npm install dotenv --save
    npm install algoliasearch --save
    npm install firebase-admin --save
    //npm install firebase --save //not here

It seems that I need the firebase module to be able to call the method to use the token. Am I correct?

I’m not a Node.js dev so maybe I’m going about this wrong but I just wanted to bring it up because I’m sure there are other people who are going to run into this situation. I’m VERY VERY THANKFUL for the support from your forums because it is extremely helpful. If it weren’t for my older code I would still have a problem trying to call firebase.auth().authenticateWithCustomToken(token) because the module wouldn’t exist and the updated docs doesn’t mention it.

  1. Would you be able to add this to your current docs? Both the auth != null issue and the code to correct it.

I just say that to say a year ago it would’ve taken me a month or more to figure this out on my own. I actually would’ve had to go to several different meet ups for help. Now I can coast with Google and SO. But the next guy or gal who comes along might be in the same situation I was. Why not make it easier for them which in turns brings more users/customers to you.

Btw I haven’t tried your suggestions yet, I’ll try everything later this afternoon and get back to you with the results.

1 Like

Your assertion is almost completely correct :slight_smile:

I forgot to mention that the createCustomToken method should be called with an argument identifying the user (if you have multiple users it should be their email adress, but as you’re running this from your server you can actually put anything, like “node_server” or “backend”). This value does not have much importance right now, but that’s the username you’ll see in your Firebase dashboard and that you can use to further restrict access. In your example, any value will do.

The authenticateWithCustomToken method will accept a callback as second argument that will be called when the authentication is done (that is the completion function you talked about). Creating the token and initializing the apps are synchronous actions. Authenticating is asynchronous. And it’s only once you’re authenticated that you should start binding your rootRef.on() methods.

As for our documentation, we assumed when we wrote it that readers would have no authentication set in their Firebase, so no need to authenticate. Also as this has more to do with Firebase than with Algolia, we also thought it would be confusing to make the doc too much about Firebase.

But I understand your frustration of not being able to make it work with what is considered Firebase security best practices. I will try to update our documentation to reflect that.

Also note that I am not a Firebase employee nor expert on the subject, so all the advice I’m giving you here is mostly what worked for me, but there might be a better way to do it :slight_smile:

@pixelastic hey thanks, I’ve actually been working on this for the past couple of hours. A couple of things I noticed.

https://firebase.google.com/docs/auth/admin/verify-id-tokens

From reading the Firebase docs above it says that on the client once the user is authenticated into the app I can call:

let currentUser = FIRAuth.auth()?.currentUser
currentUser?.getTokenForcingRefresh(true) {idToken, error in
if let error = error {
// Handle error
return;
}

// Send token to your backend via HTTPS
}

Then I somehow send that idToken to my app.js file via Https.

That’s what I’ve been trying to figure out how to do for the past couple of hours. Sending the idToken as json data it isn’t the problem but where to send it is another story. But from what your saying I can use any generic string that never actually changes it just verifies that the user can be authenticated into Firebase:

var myPizzaToken = “pizzaPizzaPizza” //can be anything

//later on in callBack
firebase.auth().authenticateWithCustomToken(myPizzaToken)

It sounds like there really isn’t a need to get the token and make that https call from the client which is actually FANTASTIC for me and makes my life a whole lot easier.

The only other issue I’ve been having is I can’t seem to call .auth() and the accompanying methods on either of the objects:

admin.auth().createCustomToken(myPizzaToken)

nor

firebase.auth().authenticateWithCustomToken(myPizzaToken)

Once I type the firebase.(dot) or admin.(dot) objects the auto complete doesn’t have an auth() method. I manually type it in and get firebase.auth(). and admin.auth() and those methods don’t seem to exist on the auth() object. Any idea what the issue?

As I thought about your reasoning for not including the documentation I realized that for the most part most devs who would implement Algolia into their project would have a fairly decent idea on how to read the docs and what to do from there. From my opinion this is very high level stuff, from indexing Algolia to building a Firebase database to the connecting it to the client, I can see why one would think that some of things I’m asking a dev should know. It makes a lot of sense. :slight_smile:

I still can’t seem to get the functions to pop up but I did come across some new info that can help future users:

According the Firebase docs the way to get around the auth problem is to set the rules as:

".read": "auth.uid === 'pizzaPizza'",
".write": "auth.uid === 'pizzaPizza'"

then in the Node file when the firebase admin is initialialized use databaseAuthVariableOverride with that pizzaPizza to get authenticated:

admin.initializeApp({
  credential: admin.credential.cert(serviceAccount),
  databaseURL: "https://databaseName.firebaseio.com",
  databaseAuthVariableOverride: {
    uid: "pizzaPizza"
  }
});

Directions are here:

https://firebase.google.com/docs/database/admin/start

I got the answer from Kato on SO:

Oh, thank you, I didn’t know one could do it that way:

firebase.initializeApp({
  databaseURL: "https://databaseName.firebaseio.com",
  serviceAccount: "./serviceAccountCredentials.json",
  databaseAuthVariableOverride: {
    uid: "my-service-worker-or-user-uid"
  }
});

You just have to pass serviceAccount set to the path to your service account JSON file to be able to create an authenticated app. And you can overrid the uid used to identify the user using databaseAuthVariableOverride.uid. That is much simpler than my custom token dance.

From what I understand, if you initialize your app this way, you don’t have to require both firebase and firebase-admin and won’t need to call .auth() at all. Let me know if this works for you, I think I could even refactor my own code to use this simpler version.

@pixelastic Hey thanks. I’ve been trying to get databaseAuthVariableOverride to show up on autocomplete but that won’t show up either. I’m having some sort of other problem. I’m about to ask on SO for an answer.

I actually like your custom token dance but realized a drawback if I used the databaseAuthVariableOverride.

Using that method you have to set your firebase rules to match:

".read": "auth.uid === 'my-service-worker-or-user-uid'",
".write": "auth.uid === 'my-service-worker-or-user-uid'"

which is find and dandy as long as I keep my rules simple. If I wanted more complex rules such as:

{
 "rules": {
     "users": {
          "$uid": {
              "read": "auth.uid == $uid"
              "write": "auth.uid == $uid && root.child('users').child('verified').child('wasUserVerified').val() == true"
    }
   }
  }
}

There may be a problem because on the client side:
“auth.uid != ‘my-service-worker-or-user-uid’”

In theory since these won’t match up because the user’s real auth.uid wouldn’t be ‘my-service-worker-or-user-uid’ my users wouldn’t be able to read nor write.

I haven’t been able to try anything yet because I can’t get that new method to appear, the friggin auth() module to appear, nor the 2 methods you suggested. Smh.

But with your original dance, I won’t have to worry about these issues.

I like your design better because I can set the token to anything on the server side and I can still keep my firebase rules complex for the client side and they’ll never overlap.

Once I get this working I’ll play around with everything then send you a message with whichever worked the best.

Sure, keep me updated :slight_smile:

I’m working on a rewrite of the documentation on my side, making sure everything works smoothly both with auth disabled and enabled.

Thanks! I’ll be in contact soon

I tried using the serviceAccount options with a filepath to the JSON file but it seems that it has been actually deprecated in the latest versions. I think the custom token dance is still going to be the best way to authenticate :slight_smile:

Sounds good. That’s what I’ve been thinking about all morning because it’s easy to implement. Now if only I can get auth() and the method to use it to show up. What version of Firebase are you using? Maybe that’s the reason it’s not showing up for me. Your using an older version and I’m not.

Look at the auto complete results.

Firebase-admin:

Firebase:

firebase

I’m using firebase package v4.4.0 and firebase-admin v5.2.1

Maybe it’s simply your autocomplete being confused in your IDE but the actual .auth() method actually exists?

@pixelastic I sent an email to Firebase tech support and they’re looking into it. They asked me which text editor I’m using which led me to believe it’s possibly the editor. I’m using Sublime. I’ll try a different one and get back to you if it works or wait for their reply. The more info you can give the next developer the better it is for everyone.

Thanks and enjoy your weekend! :slight_smile:

I had a similar issue and this is how I solved it. I was using Cloud Functions, not sure if you are.

I think your code looks like the one given by Algolia and that code runs on node.js but needs changes to run on Cloud Functions, not sure if the same changes would apply if you use other servers. Cloud Functions functions need to be exported and have been written in this format

exports.nameOfFunction=yourDatabaseRef.onCreate( //your function does the action here//);

and note the use of onCreate() and not on(), you can use onWrite(), or onUpdate() read more here

Hi Wayne. I have no experience with Cloud Functions. I’m a Swift dev so even the Node.js code is outside my realm.

Could you explain more?