This describes how to authenticate with Google Datastore using Goa accessing user resources, as described in Oauth2 for Apps Script in a few lines of code (which you should read first for background).

The library, cGoa, is available under this project key.

MZx5DzNPsYjVyZaR67xXJQai_d-phDA33

Accessing user resources

Up till now, the OAuth environment has been mainly focused on apps you write to access your self, or your resources. The other use of OAuth is to get consent from users to access their resources. In this case, each individual user needs to give consent for the scopes your app app is requesting.

Goa does this by expecting to store user refresh information in the specific users property store for this script. So if you have a web app published like this

you need to add one line to your doGet() dialog. As normal, once authorized for a specific user, refreshing will be automatic by default (unless revoked)

Setting up

Even though the token properties will be stored by user, you still need to set up the app credentials once in the script properties store. This is because each user references these for the project details, even though their token and refresh information is individually stored in the user properties.

You’ll need to create an App. The dashboard/developers console can be found here. Your one time setup would look something like this. Note that you can use the same credentials and package name as used in Google Datastore service for Goa examples (which was for apps being run as you) as the template for credentials to be used for apps being run as the user.

cGoa.GoaApp.setPackage (PropertiesService.getScriptProperties()  , {
  clientId : "10xxxxxxxx3.apps.googleusercontent.com",
  clientSecret : "CxxxxxxxxiBvi",
  scopes : cGoa.GoaApp.scopesGoogleExpand (['datastore','userinfo.email']),
  service: 'google',
  packageName: 'DriverDatastore_example'
});

The example

The example includes 3 functions.

  • A doGet example for a web app.
  • An example where the token has already been setup by a one off doGet example
  • An example of consuming the token

The doGet should be published

function doGet(e) {
  return doGetDataStoreUser (e);
}

and will create a consent screen like this, from which the redirect URI can be copied and added to the App dashboard.

The patterns

There is just a slight difference (to be run as user) between this and Google Datastore service for Goa examples (to be run as owner)

  • The package is stored in the UserProperties store
  • The first item is a userClone which will set up a user version of the credentials (if this is the first time the user has been seen)
var packageName = 'DriverDatastore_example',
      userPropertyStore = PropertiesService.getUserProperties(),
      scriptPropertyStore = PropertiesService.getScriptProperties();
  
  // this starts with a package copy for a specific user if its needed
  cGoa.GoaApp.userClone(packageName, scriptPropertyStore , userPropertyStore);

Normally, once the user has done this he wont be asked for reauthorization in the future (since the tokens are refreshed automatically), but it is possible to change this behavior with

goa.remove();

once the access token has been retrieved.

the example

function doGetDataStoreUser (e) {
  
  // this is pattern for a WebApp.
  // passing the doGet parameters (or anything else)
  // will ensure they are preservered during the multiple oauth2 processes
  
  var packageName = 'DriverDatastore_example',
      userPropertyStore = PropertiesService.getUserProperties(),
      scriptPropertyStore = PropertiesService.getScriptProperties();
  
  // this starts with a package copy for a specific user if its needed
  cGoa.GoaApp.userClone(packageName, scriptPropertyStore , userPropertyStore);
  
  // create a user specific package. 
  var goa = cGoa.GoaApp.createGoa (packageName,userPropertyStore).execute(e);
  
  // it's possible that we need consent - this will cause a consent dialog
  if (goa.needsConsent()) {
    return goa.getConsent();
  }
  
  // if we get here its time for your webapp to run and we should have a token, or thrown an error somewhere
  if (!goa.hasToken()) throw 'something went wrong with goa - did you check if consent was needed?';
  
  // This is a webapp doing whaever its supposed to do
  // getParams is used to retrieve the original parameters passed to this function
  var result = testDataStore (goa.getToken(), goa.getParams() );   
  
  // remove package -- if you want to disable access to the service for the user and need continuous auth dalog uncomment this...
  ////goa.remove();
  
  // now return it as normal
  return HtmlService.createHtmlOutput (result.getContentText())
    .setSandboxMode(HtmlService.SandboxMode.IFRAME);

}


function dataStoreUser(params) {
       
  // pick up the token refreshing if necessary
  var goa = cGoa.GoaApp.createGoa('DriverDatastore_example', PropertiesService.getUserProperties()).execute(params);
  
  if (!goa.hasToken()) {
    throw 'for a non webapp version - first publish once off to provoke a dialog - token will be refreshed automatically thereafter';
  }
  
  // do a test - passing the token and any parameters that arrived to this function
  Logger.log (testDataStore (goa.getToken(), goa.getParams() ));
  
} 

/**
 * this is your main processing - will be called with your access token
 * @param {string} accessToken - the accessToken
 * @param {*} params any params
 */
function testDataStore (accessToken,params) {
 
   var options = {
     method: "POST",
     contentType : "application/json" ,
     muteHttpExceptions : true,
     headers: {
       "authorization": "Bearer " + accessToken,
     },
     payload:JSON.stringify({
       "query": {
        "kinds": [{"name":"polymerdbab"}]
       }
      })
   };
   

  return UrlFetchApp.fetch( 
    "https://www.googleapis.com/datastore/v1beta2/datasets/xliberationdatastore/runQuery", options);

}
For more like this, see OAuth2 for Apps Script in a few lines of code
Why not join our forum, follow the blog or follow me on Twitter to ensure you get updates when they are available.