OAuth2 for Apps Script in a few lines of code

When I first started working with OAuth2, some years ago it was super complicated. Since that time, Apps Script has added a few capabilities which makes it possible to create a library with a minimal footprint in the App that uses it. My previous library, cEzyOauth2, like some others, contained a number of functions to service these complications (EzyOauth2 patterns), meaning that scripts using the library needed a fair amount of boilerplate code included in addition to the library.

This new library, that I call Goa, has these main features.
  • The 'in app' pattern is just a few lines of code.
  • No need to worry about handling callbacks. The new pattern manages the process behind the scenes.
  • Knows how to authenticate to a large number of services, such as Google, Microsoft Live, Github, Soundcloud, Reddit and many others.
  • Can handle both regular OAuth2 flow and service accounts.
  • Refreshing expired tokens in automatic and doesn't generate a dialog. 
  • Credentials and token handling information are stored in the properties service and, each are identified by a unique name
  • Can handle  authentication flows to multiple services in the same app.
  • Authentication dialogs can be webapp, sidebars or dialogs.
  • Authorization can be for either 'user running script' and 'script owner'
  • It's compatible with cEzyOauth2 and can use the same credentials.
  • It also supports jwt for firebase authorization
The library, cGoa, is available under this project key, or on github. All the tests described for this topic are also available on github.

MZx5DzNPsYjVyZaR67xXJQai_d-phDA33

Note:

In August 2016, the getProjectKey method was marked as deprecated, so the goa library (v22 and above) has been updated to use the scriptid rather than the projectKey for redirectUrl purposes.


for a quick run through of multiple scenarios using Google Apis, take a look at this summary

Quick summary of all OAuth2 scenarios

Or these more detailed tutorial slides

Goa tutorial


Some walkthroughs

For quick examples of complete apps using different ways of getting access tokens see Some OAUTH2 walkthroughs

Example

Typically, an authentication dialog is needed only once in the script lifecycle. Thereafter (if the API allows it), tokens will be refreshed automatically in the background. Service Accounts never need a dialog. 

For this example, a token that has been created by an authentication dialog (for Microsoft OneDrive) will be refreshed as required and returned. 
function oneDrive(params) {
       
  // pick up the token refreshing if necessary
  var goa = cGoa.GoaApp.createGoa('onedrive', PropertiesService.getScriptProperties()).execute(params);
  
  // This is a webapp, so your function should return the HtmlOutput result of your process
  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 (testOneDrive (goa.getToken(), goa.getParams() ));
  

so it can be used to get quota information from OneDrive 
function testOneDrive (accessToken,params) {
 
   // get the quota from the one drive API as a token test.
   var options = {
     method: "GET",
     headers: {
       authorization: "Bearer " + accessToken
     }
   };

  return UrlFetchApp.fetch("https://apis.live.net/v5.0/me/skydrive/quota", options);

}

And that's all that's needed in the script. See the bottom of this page for full examples for a selection of services.

Authentication Dialog.

If an authentication dialog is required (or potentially required), a slightly different pattern is required to handle the callback from the Oauth2 environment. 

This example, shown implemented in a webapp, will initiate a dialog if necessary,  populate the token data for future use and run a query on Google Datastore as the main function of the webapp.
function doGet(e) {  
  return doGetDataStore (e);
}

function doGetDataStore (e) {
  
  // this is pattern for a WebApp.
  // passing the doGet parameters (or anything else)
  // will ensure they are preservered during the multiple oauth2 processes
  
  // change this to whatever store & credentials name are being used
  var goa = cGoa.GoaApp.createGoa('DriverDatastore_example',PropertiesService.getScriptProperties()).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() );   
  
  // now return it as normal
  return HtmlService.createHtmlOutput (result.getContentText())
    .setSandboxMode(HtmlService.SandboxMode.IFRAME);

}


A Goa authentication dialog looks like this.

Clicking Start will kick off the familiar Google Authorization screen.




Setting up the credentials

Each service will have its own dashboard for application credentials. Google has the developer console. You'll notice that the redirect Url for your script is shown in the Goa dialog. This is the one that should be entered into the developer console for the appropriate service.

A one time setup for each credential set looks like this. Here's one for GitHub.  Note that Google APIS can take their credentials directly from a downloadable file.
  var propertyStore = PropertiesService.getScriptProperties();
     
     cGoa.GoaApp.setPackage (propertyStore ,{ 
    clientId : "7------------0c",
    clientSecret : "3------------------d",
    scopes : [
      'gist',
      'repo'
    ],
    service: 'github',
    packageName: 'githubgoa'
  });



Google Scope shortcuts

Scopes are included in the package, and can be defined using the fully qualified Google scopes, such as 
scopes:["https://www.googleapis.com/auth/drive" , "https://www.googleapis.com/auth/userinfo.email"]

There is a utility function so you can just say 'drive' or 'spreadsheets' if you prefer. Here's the entry for the DataStore.
  cGoa.GoaApp.setPackage (propertyStore , { 
    clientId : "1---------a3.apps.googleusercontent.com",
    clientSecret : "CH----------iBvi",
    scopes : cGoa.GoaApp.scopesGoogleExpand (['datastore','userinfo.email']),
    service: 'google',
    packageName: 'DriverDatastore_example'
  });

Google Service accounts


Using service accounts is no different that the regular oAuth flow, except there is never an authentication dialog. Refreshing is automatic just before. Here's an example of using the datastore with a service account. 

function dataStoreSA (params) {
       
  // pick up the token refreshing if necessary
  var goa = cGoa.GoaApp.createGoa('DriverDatastore_serviceaccount', PropertiesService.getScriptProperties()).execute(params);
  
  // This is a webapp, so your function should return the HtmlOutput result of your process
  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() ));
  


The setup for a service account is different in the Developer console. You should download the JSON file containing the private key as advised when you set up the access, take a note of its fileId, and do a one time set up like this. Once you have done this one time setup, the file is no longer referenced as its contents are stored in the selected Properties Service.

  cGoa.GoaApp.setPackage (propertyStore , cGoa.GoApp.createServiceAccount (DriveApp , {
    packageName: 'DriverDatastore_serviceaccount',
    fileId:'0B92ExLh4POiZN3YyMGZMUTdMN2s',
    scopes : cGoa.GoaApp.scopesGoogleExpand (['datastore','userinfo.email']),
    service:'google_service'
  }));

Here's some more detailed information on this library and setting things up to use Goa.

Examples of setting up and consuming various services

These examples demonstrate running as the owner of the script. (accessing script owner resources)

For more like this, see Google Apps Scripts snippets. Why not join our forumfollow the blog or follow me on twitter to ensure you get updates when they are available. 





Subpages (27): View All
Comments