Writing a Personal Automation API: Part 3 - Authentication

Posted by Matthew Watkins on December 21, 2016

In my previous post, I outlined how to start building a framework for a personal automation API hosted on Google Apps Script. With the code from the last tutorial, we’re almost ready to start adding in services and integrations to the API. but before we get to that point, there’s still a little bit of work left to do on the framework: authentication (and all the helpers we’ll need to accomplish that)

Data persistence

In the last post, our authorize() function simply returned true. But now that we want to add “real” authentication, we’ll need a way to persist data.Google Apps Script doesn’t give us a proper database to speak of. If you have a SQL database somewhere it can hit, you can use that. if not, Google offers their own paid service that lets you host a MySQL database if you want to put down some dough. But if you’re using Google Apps Script for this personal project in the first place, you probably aren’t looking to invest real dollars into this quite yet. Plus, for the type of personal API we’re building, we really don’t need to store a ton of data– just a few kilobytes to store basic things like allowed auth tokens and stuff.

Wrapping the PropertiesService

Fortunately, Google gives us the PropertiesService– a small key value storage system that will let you perform basic CRUD operations. Definitely not intended to be a “database” per se, but for our purposes, we’re going to use it as a very small NoSQL solution and store everything we need to persist in there as JSON. If you’re worried about scale and concurrency, you can probably get creative with having it read and write from Google Sheets and use that as your “database”, but until I reach the point where I’ll need to do something like that, I’m happy just storing JSON objects in this workaround.

Here is what I wrote to be the DbUtility for my personal API:

/*
 * "Constructor" for the DbUtility
 */
function DbUtility() {
  this.storage = PropertiesService.getScriptProperties();
  
  /*
  * Retrieves the value of the specified key (null if not found)
  */
  this.getObject = function (key) {
    var valueJSON = this.storage.getProperty(key);
    return valueJSON ? JSON.parse(valueJSON) : null;
  };
  
  /*
   * Sets the value of the specified key
   */
  this.setObject = function (key, value) {
    var valueJSON = JSON.stringify(value);
    this.storage.setProperty(key, valueJSON);
  };
  
  /*
   * Gets the value of the specified key, creating it in the storage if not found
   */
  this.getOrCreate = function (key, defaultValueObj) {
    var valObj = this.getObject(key);
    if (!valObj) {
      this.setObject(key, defaultValueObj);
      valObj = defaultValueObj;
    }
    return valObj;
  };
}

You can see the code is pretty basic– a get, a create, and a getter that auto-creates if the key doesn’t already exist. The important thing is that it handles the serialization and parsing of the JSON for us so as far as the caller is concerned it’s just storing and retrieving objects.

Token generation

So now that we have a data storage facility where we will eventually keep our tokens, we’ll need some way to generate tokens. For this, I figured a GUID is just as good as anything else for our purposes, so I copied this StackOverflow answer to create a GuidUtility:

/*
 * "Constructor" for the GuidUtility
 */
function GuidUtility() {
  /*
  * Generates a new GUID (from http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript)
  */
  this.generate = function () {
    function s4() {
      return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
    } 
    return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
  };
}

Tying it together into an authenticator

Finally, let’s tie it all together into one utility that can manage our tokens, SecurityUtility:

/*
 * "Constructor" for the SecurityUtility
 */
function SecurityUtility() {
  /*
  * Adds the specified token to the authorized list
  */
  this.addToken = function (token) {
    var authorizedTokens = this.getAuthorizedTokens();
    authorizedTokens.push(token);
    this.setAuthorizedTokens(authorizedTokens);
  };
  
  /*
  * Returns whether or not the token is on the authorized list
  */
  this.isAuthorized = function (token) {
    var allTokens = this.getAuthorizedTokens();
    for (var i in allTokens) {
      if (allTokens[i] === token) {
        return true;
      }
    }
    return false;
  };
  
  /*
  * Returns all authorized tokens for the application
  */
  this.getAuthorizedTokens = function () {
    return new DbUtility().getOrCreate('SecurityUtility.Tokens', []);
  };
  
  /*
  * Sets the list of all authorized tokens for the application
  */
  this.setAuthorizedTokens = function (tokens) {
    return new DbUtility().set('SecurityUtility.Tokens', tokens || []);
  };
}

Then we just swap out that “return true” from our authorize() function in our request handler and replace it with a call to our new SecurityUtility:

/*
* Checks the user's auth token against the allowed tokens. Throws an authorization exception if not allowed
*/
this.authorize = function(authToken) {
  if (!(new SecurityUtility().isAuthorized(authToken))) {
    throw new WebApiException(ErrorCode.UNAUTHORIZED, 'That auth_token is not authorized'); 
  }
};

Generating a new token

To generate tokens, I just wrote a method in Main.gs that will generate new tokens for us. I run it from the script as needed whenever I need to add a new device to my automation suite:

/*
 * Token generation
 */
function generateNewToken() {
  var newToken = new GuidUtility().generate();
  new SecurityUtility().addToken(newToken);
  Logger.log('Added new auth token ' + newToken);
}

Select this function at the top of the screen and run it (play button) to generate a new token. Hit Ctrl+Enter to see the logs and see your new token. Alternately, you can read, write, or delete all your properties (our fake DB) using File -> Project Properties -> Script Properties. Use the request instructions from the previous post to test out your authentication piece.

This post first appeared on Another Dev Blog