Use Firebase with Google Apps Script
INTRO
Why you might want to do this is not obvious, given that there are already at least two methods (firebase component or REST API with web component) to manage firebase realtime database data, however, it provides a couple of different routes for authentication / secure rules that may be more compatible with your app, and it may also simplify firebase usage.
You can authenticate with a firebase project, using google apps script, with one of two approaches:
Use the now deprecated, but still functional, legacy secret key
Use a service account and get an auth user token (this is the google preferred method)
If you have no secure rules in place (read:true, write:true), then you do not need to use either of the above
I will assume you have created a new firebase project, and set the rules to read:true, write:true. It will help if you add some same data in the console.
Setting up the Google Apps Script Web App
PART 1
Let us first setup an environment with no rules or authentication. We will need a FirebaseApp library and also ensure that our appsscript.json is correctly formatted.
FirebaseApp Library ID = "1hguuh4Zx72XVC1Zldm_vTtcUUKUA6iBUOoGnJUWLfqDWx5WlOJHqYkrt"
In the script editor, click on the + next to Libraries
A popup box opens, copy the ID above, and paste this into the ScriptID textbox, then click on Look up
Then click on Add. Your library will be added, you can see it below "Libraries". (it is best to select the latest version number for your library)
Now click on the cog at the bottom of the icon list on the left. This will open Project Settings
Tick the checkbox to show "appsscript.json", then return to the editor
Your appsscript.json file should look like this. You may have a different time zone, or a different version number for the library
Now, in your project editor, delete whatever is there and replace it with this, obviously using your own firebaseUrl;
var firebaseUrl = "https://fb-gas-default-rtdb.firebaseio.com/";
var base = FirebaseApp.getDatabaseByUrl(firebaseUrl);
function doGet(e) {
if (e.parameter.func == "allRecords") {
var records = JSON.stringify(base.getData());
return ContentService.createTextOutput(records);
}
}
Now you can deploy your project as a web app and get the script url
Create a simple AI2 app, with a button and a label and drag in a web component. Setup your blocks like so:
Run in companion app, and then press on the Return All Data button, and you should see a json of all your data - you can then work on this using the dictionary blocks:
If we wanted to return just a single record, then we would need to add another function to the script and write some more blocks:
if (e.parameter.func == "readRecord") {
var record = JSON.stringify(base.getData(e.parameter.pb + "/" + e.parameter.key));
return ContentService.createTextOutput(record);
}
and let us return the record for John Smith:
Let us add a record now. We will use POST for this, so we need to add a doPost(e) function to our web app. Notice in the blocks that we set some parts as parameters, and other parts in a json. Once you get the hang of this, you will be up and down the firebase data tree like a monkey :)
function doPost(e) {
if (e.parameter.func == "addRecord") {
var recordData = JSON.parse(e.postData.contents);
base.setData(e.parameter.pb +"/" + e.parameter.key, recordData);
return ContentService.createTextOutput("new record for " + e.parameter.key + " added to Project Bucket " + e.parameter.pb + " on firebase");
}
}
To update a record you can use the updateData() method with POST, this helps to avoid overwriting any existing data. To delete a record you can use either the removeData() method, or use setData() and set an empty json - {}. pushData() will create a record but generate a unique id. getAllData() will return the data for records at specified paths. You can also run a query, but you may need some indexing on the data to be able to do this.
The FirebaseApp provides the following commands:
getData(path, optQueryParameters)
getAllData(requests)
pushData (path, data, optQueryParameters)
setData(path, data, optQueryParameters)
updateData(path, data, optQueryParameters)
removeData(path, optQueryParameters)
PART 2
Using the Firebase Project Secret Key to Authenticate
This is where the fun starts, and provides the AI2 app developer with fine control within the app over who can access what data. we use the Database secrets key, along with the FirebaseApp library, to give the equivalent of firebase console access to google apps script, and consequently the AI2 app. The first step is to change the read/write rules to false. No-one outside of being able to use the AI2 app or the google apps script can access the data once this is done. Developers will want to include controls in the AI2 app, and google apps script, to ensure only those trusted or allowed access, have access. You do not really want to make your secret key available to anyone.
Firstly, change the rules:
Then, click the cog next to Project Overview, then click on Project Settings
Select the Service Accounts tab. 2. Click on Database secrets. 3. Copy the secret (Show and Copy buttons appear on hover).
Insert the following lines at the top of your script, under the "var base = ...." line
var secret = "LGJ43O92PsOGbPSRD8fbAzxe2kGAxknbeh1hljmA"; << this is a fake secret key...
var baseSecret = FirebaseApp.getDatabaseByUrl(firebaseUrl,secret);
Now, look through your code and find the lines that reference the variable "base". You need to replace "base" with "baseSecret". For example, change:
if (e.parameter.func == "allRecords") {
var records = JSON.stringify(base.getData());
return ContentService.createTextOutput(records);
}
to this:
if (e.parameter.func == "allRecords") {
var records = JSON.stringify(baseSecret.getData());
return ContentService.createTextOutput(records);
}
Re-deploy your web app to a new version. You should now be able to run your AI2 app as before, getting and setting data, even though the rules are set to false. You have god powers over your firebase data from your app! Yet no-one outside of your app can get at the data. You will obviously need to put some controls in place to ensure that only trusted users have this ability, and that your app does not allow full access (e.g. DELETE) to all data.
PART 3
Using a service account to generate a user token to Authenticate
This method has an end result much the same as the one in PART 2, although it does generate a different userToken for each access, but the service account's private key is needed, which again, you do not really want to share. it may be better to store it in Script.Properties.. The setup is also a little more complex. Make sure you have at least read through PART 2, which explains and shows images of some of the actions required.
Go to Project Settings again, then select the Service Accounts tab. you should see a default service account listed on the right. You want to click on the blue button at the bottom to generate a new private key. Do this, it will download a json file to your computer. Open the json file and copy the PRIVATE KEY,
from "-----BEGIN PRIVATE KEY-----" to "-----END PRIVATE KEY-----\n". In your google apps script, go to the bottom of the script, and create a new "var" called PRIVATE_KEY, and paste the copied private key as a string. It should then look like this:
var PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEF....jV+vWtRYPOYk6CnQQxPWhc=\n-----END PRIVATE KEY-----\n';Below this, create another "var" and add the service account email address like so:
var CLIENT_EMAIL = 'firebase-adminsdk-wj2ww@fb-gas.iam.gserviceaccount.com';Now below this paste the following functions
function getFirebaseService() {
return OAuth2.createService('Firebase')
// Set the endpoint URL.
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
// Set the private key and issuer.
.setPrivateKey(PRIVATE_KEY)
.setIssuer(CLIENT_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');
}
function reset() {
var service = getFirebaseService();
service.reset();
}Go back to the top of the script, and paste the following below the first four lines:
var service = getFirebaseService();
token = service.getAccessToken();
var baseToken = FirebaseApp.getDatabaseByUrl(firebaseUrl,token);Go through your script function calls again, changing "base" or "baseSecret" to "baseToken", just as you did for using the secret key
Now you need to add another library. Click on the + next to Libraries and paste the following ID:
0Auth2 ID = "1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF"
and add the library, using the latest version. This should update your appsscript .json file accordingly.Back to your code, you now what to surround the contents of your doGet() and doPost(e) with an if statement, to check that the service has access, like so:
function doGet(e) {
if (service.hasAccess()) {
if (e.parameter.func == "allRecords") {
var records = JSON.stringify(baseToken.getData());
return ContentService.createTextOutput(records);
}
if (e.parameter.func == "readRecord") {
var record = JSON.stringify(baseToken.getData(e.parameter.pb + "/" + e.parameter.key));
return ContentService.createTextOutput(record);
}
}
}Finally you want to re-deploy your web app again, and test your AI2 app to ensure everything is working.
As previously mentioned, the private key of your service account should never be shared. In the example above, it is saved directly as a global variable in the source code but you can decide to store it somewhere else (eg: in a Script Property).