Using secured client-side authentication for real time read and write calls
Goal
This tutorial shows how to authenticate your users to Firebase from a published webapp. You will then allow the user to create a secure websocket connection to your Firebase database. This connection will allow the user to make real time read and write calls to areas in the database secured by security rules written by you in your Firebase project.
Prerequisites
Before beginning this tutorial, you should have created an account on Firebase and created a new database from this account. You should have also added our FirebaseApp library for Apps Script in a new Apps Script project (see installation).
Overview
Connect to your firebase project and generate an authentication token
Add Authentication security and rules to your Firebase database
Read and write data to privileged locations in your firebase
Connect to your firebase project and generate an authentication token
Firebase gives granular access to each property in your database using the Security and Rules tab in your Firebase project dashboard. This allows you to grant users the right to read and write to only specific locations based on your security needs. You already have the user authenticated to your Apps Script. FirebaseApp allows you to use that authentication to log the user into your Firebase database.
Copy the following code below into your Apps Script server side code then replace the firebaseUrl and secret with your own. If you need help finding your Firebase Secret refer the Appendix A at the end of this tutorial.
function makeToken(){
var firebaseUrl = "https://script-examples.firebaseio.com/";
var secret = "Vt0nb3HnLOK6wImGbG1PxZnBAK4lLN2ysklZyF5Z";
var base = FirebaseApp.getDatabaseByUrl(firebaseUrl, secret);
var token = base.createAuthToken(Session.getActiveUser().getEmail());
return token;
}
In your Webapp you will need to make a call to this function. Use the following code below. Make sure your replace firebaseUrl with your own.
<script src="https://cdn.firebase.com/js/client/2.2.7/firebase.js"></script>
<script>
var userAuthToken;
google.script.run.withSuccessHandler(function (requestAuthToken) {
userAuthToken = authenticateClient(requestAuthToken)
}).makeToken();
function authenticateClient(userRequestToken) {
var firebaseUrl = ‘https: //script-examples.firebaseio.com/’;
var ref = new Firebase(firebaseUrl);
ref.authWithCustomToken(userRequestToken, function (error, authData) {
if (error) {
console.log("Login Failed!", error);
}
else {
console.log("Login Succeeded!", authData);
}
});
return ref.authData.auth;
}
</script>
Lets break this down.
The authentication flow works as follows.
The user is authenticated to your Apps Script. The webapp is launched.
The webapp loads the firebase client side library and requests a token from the server side of your Apps Script.
The server generates an Authorization Request Token and sends it to the webapp.
The webapp sends the Authorization Request Token to the firebase server and receives back a secure websocket connection.
If the authentication is successful and the secure websocket connection is made all requests made to the Firebase project database will automatically be authenticated requests
Add Authentication security and rules to your Firebase database
Imagine your Firebase database has a property of users where you store information about your users. You may even have some sensitive information such as post addresses or mobile numbers saved here. By default your Firebase database was given read and write rules.
These default rules give global access to anyone in any property of your database. This means that anyone can connect to your database and read or write any data. If you used this default setting it would not be long before someone would run the following two lines of code:
var unsecuredDatabase = new Firebase("https: //script-examples.firebaseio.com/users");
unsecuredDatabase.on(‘value’, function (allUsersData) {
console.log(allUsersData.val());
});
These two lines of code would allow the curious (possibly even well meaning) programmer to dump the entire contents of the users property to the console exposing any information stored. To be clear this would be VERY bad.
Fortunately, you now know how to authenticate your valid users. You now need to set up the security rules to grant access to the user where necessary in your database and restrict access to data to unauthenticated users . These rules can be created in the the Security and Rules tab of your Firebase project dashboard.
These rules are created in easy to read and write JSON format. They also have access to some special build in data objects such as ‘auth’ as well as variables designated by the $ token. For in depth documentation for Firebase security see the official documentation.
For this tutorial use the following security rule. Don’t forget to click Save Rules!
{
"rules": {
"users": {
"$user_id": {
".write": "$user_id === auth.uid",
".read": "$user_id === auth.uid"
}
}
}
}
Lets break this down.
All rules are children of the rules property.
We add the property of users.
Note: This property does not need to exist yet in your Firebase database. It will be created when we save our first object to it.
We create a variable property called $user_id as a child to the users property.
Variable names can be anything ie ($user_type, $isAdmin, $Likes_Sports) as long as they have the $ token as a prefix
.read and .write are built in rule interfaces that need to be set by providing a true or false statement. In this case we compare against the built in object ‘auth’.
The auth object will contain the user id (uid) and any additional properties specified in FirebaseApp.createAuthToken(userEmail, additionalProperties).
See Appendix A on additionalProperties. We specifically are saying give read and write access to this property if the user’s auth token’s uid value matches the property name.
Lets walk through an example:
We generate a token and authenticate for user MWazowski@monsters.inc.
The auth object will contain the property of uid with the value of mwazowskimonstersinc
We attempt to read from https://script-examples.firebaseio.com/users/jsullivanmonstersinc
Firebase sets the variable $user_id to jsullivanmonstersinc and compares it to the auth object uid value of mwazowskimonstersinc
Firebase returns that the user cannot read or write to this location
We attempt to read from https://script-examples.firebaseio.com/users/mwazowskimonstersinc
Firebase set the variable $user_id to mwazowskimonstersinc and compares it to the auth object uid value of mwazowskimonstersinc
Firebase returns that the user can both read and write to this location
Read and write data to privileged locations in your firebase
Now that you have successfully authenticated the user in the webapp you can make read and write calls to protected locations in your Firebase database.
Let’s register the user in our Firebase database and keep track of when the user is online or offline.
Copy the following code to your server side App Script code. Make sure to set the firebaseUrl and secret to your own.
function registerClient(auth) {
var firebaseUrl = "https://script-examples.firebaseio.com/";
var secret = "Vt0nb3HnLOK6wImGbG1PxZnBAK4lLN2ysklZyF5Z";
var base = FirebaseApp.getDatabaseByUrl(firebaseUrl, secret)
var currentUser = base.getData("/users/" + auth.uid);
if (currentUser) {
base.updateData("/users/" + auth.uid, auth);
}
else {
base.setData("/users/" + auth.uid, auth);
}
return auth.uid;
}
Lets add a new function to our client side code.
function trackOnlineStatus(user) {
var firebaseUrl = ‘https: //script-examples.firebaseio.com/’;
var onlineStatus = new Firebase(firebaseURL + '/.info/connected');
var myConnections = new Firebase(firebaseURL + '/users/' + user + '/connections');
var lastOnlineTimestamp = new Firebase(firebaseURL + '/users/' + user + '/lastOnline');
onlineStatus.on('value', function (online) {
if (online.val() === true) {
var connection = myConnections.push(true);
connection.onDisconnect().remove();
lastOnlineTimestamp.onDisconnect().set(Firebase.ServerValue.TIMESTAMP);
}
});
}
On the client side code modify the initial makeToken() request as follows:
google.script.run.withSuccessHandler(function (requestAuthToken) {
userAuthToken = authenticateClient(requestAuthToken);
google.script.run.withSuccessHandler(trackOnlineStatus).registerClient(userAuthToken);
}).makeToken();
Lets break this down.
As soon the webapp loads it will make a google.script.run call to the server function makeToken();
The request token will be sent to Firebase and the user will get an authenticated connection and token to your Firebase database
A new google.script.run is called with this new token and send to the server side function registerClient(authToken)
The registerClient function will return the user id (uid) to the trackOnlineStatus function
trackOnlineStatus opens three connections: /users/$user_id/connections, /users/$user_id/lastOnline, and to a special endpoint /.info/connected
/.info/connected endpoint will return true if the current user has a valid websocket connection to the Firebase database
Using the on() method of onlineStatus you can watch any changes to the online status of the user.
If the returned value of on() is true the user is online and we add an item to the connections property under that users own child property of users.
We setup a onDisconnect() callback for that item and pass it the built in function of remove(). This simply mean that when the user is disconnected it will remove that item from the connections property.
NOTE: You may notice that if the user launches your webapp from multiple tabs or browsers there will be an entry for each active connection.
We also request a timestamp be saved on the lastOnline property if the user disconnects from the server.
Appendix A
How to find your Secret Key
To obtain your secret key you will need to open the dashboard for your Firebase project. You will find the secret under the Secrets tab.
Additional Properties in the Auth Token
You can add additional properties that will be stored in the user’s Auth token and added to the built in Security & Rules Auth object.
Consider the following snipped of code:
var requestToken = FirebaseApp.createAuthToken(‘person@example.com’, {“isAdmin”:”true”, “displayName”:”Iama Person”, “userAge”:”35”});
After this request token has been passed to the client and the client has used it to create an authenticated connection to Firebase the additional properties can be used to check for access in your Security & Rules settings.
For example:
{
"rules": {
"webappSettings": {
".write": "auth.isAdmin",
".read": "auth.isAdmin"
},
"unmoderatedChat": {
".write": "auth.userAge > 17",
".read": "auth.userAge > 17"
}
}
}
If the user attempts to read or write to the webappSettings property (or any of its children) it will look in the auth object for the isAdmin property value. Additionally if the user attempts to read or write to the unmoderatedChat it will check the userAge property and only allow access of it is over 17.
As you can see adding additional properties to the user’s auth token adds a lot of granular access to the data stored in your firebase database.