robe007
robe007

Reputation: 3927

Firebase Database Secrets on Google Apps Script

So, first of all, I'm asking this question because I want that someone admits or denies -whats for me is a myth- about to use or not to use the deprecated firebase database secrets.

Why this? Well, in the Service Accounts tab from the Firebase console, I can read this:

Databse secrets are currently deprecated ans use a legacy Firebase token generator. Update your source code with the Firebase Admin SDK

But belows this advice, I can currently see my secret key. Why is this possible, if the use of this secret key is actually deprecated?

But this story, does not ends here. If a search for tutorials using Google Apps Script (even on officials), they use the deprecated database secret key, but you can read this:

Google warns that this authentication method is deprecated. In fact you can still use this method but standard Service Accounts are more secure and are recommended. As Database secrets are easier to use, far from really deprecated and safe enough for most use cases, that's what we will use here.

Again, how is this possible?

I want to say that I have tried:

function myFunction() {
  var firebaseUrl = "https://myApp.firebaseio.com/";
  var secret = "theKey";
  var database = FirebaseApp.getDatabaseByUrl(firebaseUrl, secret);
  Logger.log(database.getData());
}

and it works, because I cann see the data. Why if the secret is present? This brings me to think that the use of the secret key is allowed, even it is deprecated

Any opinions?

Upvotes: 3

Views: 4754

Answers (3)

jpp
jpp

Reputation: 349

I don't think so but may be anybody still needs some help on this.

If you need to replace "pin to pin" the deprecated secret because its sunset comes, the best way I found is this:

Use this to get the db

   let token = ScriptApp.getOAuthToken();
   let db = FirebaseApp.getDatabaseByUrl(dbUrl, token);

This will get a token for the current user (the user who is running or the user in the name of the script is running). This user must be added at https://console.firebase.google.com/project/<<projectName>>/settings/iam where you can set permissons like owner, editor, viewer...

Oh... the rules... the rules are the super basic you may were using with the secret.

   {
        "rules": {
            ".read": false,
            ".write": false
        }
    }

Upvotes: 1

robe007
robe007

Reputation: 3927

Well, now that some users have been putting an interest -more than a full year later- in the question (consider also the answer from Chris W), I post the method using the GSApp library, that works also without a databse secret:

  1. Install the GSApp library. To do this, go to Resources tab, then click on Libraries and paste the libraryId: MJ5317VIFJyKpi9HCkXOfS0MLm9v2IJHf

  2. Create a Service Account:

    • Create a new API Project in your developers console, or use one associated with your script.
    • Select APIs and Auth -> Credentials.
    • Click 'Create New Client Id'.
    • Select Service Account and Generate new JSON key.
    • Click 'Create Client Id'. The JSON key will autodownload.
    • Click 'APIs' in left menu.
    • Enable any APIs you need this service account to access.
  3. Copy the contents of the downloaded JSON key file and paste it into your scripts properties. To do this, go to File -> Project properties -> Script properties tab. There in the field Property type service_account (or however you want to name it), and in the Value field paste all the content of the JSON key file.

  4. Use the following code as an example:

    function myFunction() {
      var firebaseUrl = "https://YOUR-FIREBASE-PROJECT.firebaseio.com/";
    
      var jsonKey = JSON.parse(PropertiesService.getScriptProperties().getProperty("service_account"));
      var key = jsonKey.private_key;
      var clientEmail = jsonKey.client_email;  
    
      var serverToken = new GSApp.init(key, ['https://www.googleapis.com/auth/firebase.database', 'https://www.googleapis.com/auth/userinfo.email'], clientEmail);
    
      serverToken.addUser(clientEmail).requestToken();
      var token = serverToken.getToken(clientEmail).token;
    
      var dataPath = 'YOUR-FIREBASE-DATA-PATH';
      var options = {};
      var result = UrlFetchApp.fetch( firebaseUrl  + dataPath + '.json' + '?access_token=' + token, options).getContentText();
      var data = JSON.parse(result);
      var keys = Object.keys(data);
    
      var sheetRow = [];
      var entryKeys;
    
      for (index in keys) {
       sheetRow = [];
       entryKeys = Object.keys(data[keys[index]])
       for (i in entryKeys) {
        Logger.log(entryKeys[i]);
       }
      }
    }
    

You get the data from firebase as a json object, this means that you need to go through all the object's properties by hand (forEach, map, for ... of).

Upvotes: 2

Chris
Chris

Reputation: 2107

So I had this issue a full year and a half later, and you are right, the information out there for using Firebase with Apps Script is outdated and misleading. I figured it out and am trying to update the information in StackOverflow and the tutorials.

But this story, does not ends here. If a search for tutorials using Google Apps Script (even on officials), they use the deprecated database secret key, but you can read this...

This link you added here is actually a 3rd party website by a dude named @RomainVialard. His stuff has been very useful to me in the past. While the tutorials on his website have gotten outdated, the library he created still functions wonderfully and was updated semi-recently.


The Issue (Server-side) - Answer to OP's Question

So for the server-side piece, I used the official-ish OAuth2 Library to generate access tokens from a service account with a private key: https://console.firebase.google.com/u/0/project/<YOUR-PROJECT-ID>/settings/serviceaccounts/adminsdk. I then created a function that retrieves the token from the oauth2 library:

function getFirebaseService() {  
  return OAuth2.createService('Firebase')
      // Set the endpoint URL.
      .setTokenUrl('https://accounts.google.com/o/oauth2/token')

      // Set the private key and issuer. I stored mine in the PropertiesService... You could just use a variable somewhere.
      .setPrivateKey(fb_PRIVATE_KEY) //Service account private key
      .setIssuer(fb_SERVICE_EMAIL) //Service account email

      // Set the property store where authorized tokens should be persisted.
      .setPropertyStore(PropertiesService.getScriptProperties())

      // Set the scopes.
      .setScope('https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/firebase.database');
}

With this code, I can then call getFirebaseService().getAccessToken() which returns a valid 'admin' or Editor level access token that can be used in a Firebase call! I use the FirebaseApp library because it's easy!

var service = getFirebaseService();
  if (service.hasAccess()) {
    var fb = FirebaseApp.getDatabaseByUrl(fb_URL, service.getAccessToken());
    //Success! Do what ever with Firebase!!

  } else {
    Logger.log("makeToken: " + service.getLastError());
  }

You can also generate a client authentication token to use client-side if needed: FirebaseApp.getDatabaseByUrl(fb_URL, service.getAccessToken()).createAuthToken(USER_EMAIL);.


Another Common Issue (Client-side)

The main problem I had to overcome in my question, was that Firebase Admin SDK has been upgraded to 3.x. If you update the tutorial code to use the 3.x version everything works wonderfully!

// Initialize Firebase
var config = {
  apiKey: "<Web API Key>",
  authDomain: "<Project ID>.firebaseapp.com",
  databaseURL: "https://<DB URL>.firebaseio.com/"
  };

firebase.initializeApp(config);

//userRequestToken is retrieved from server-side Romain's FirebaseApp Library linked above.
firebase.auth().signInWithCustomToken(userRequestToken).catch(function(error) {
    // Handle Errors here.
    console.error("authClient: ", error.code, error.message);
  });

  return {
    uid: firebase.auth().currentUser.uid,
    metadata: {
      lastSignInTime: firebase.auth().currentUser.lastSignInTime
    }
  }; 

Upvotes: 4

Related Questions