Authentication dialog

This describes how to set up an authentication dialog using the Goa library as described in Oauth2 for Apps Script in a few lines of code (which you should read first for background).

If you have initialized the credentials as described in Goa Setup, you are now ready to set your app for an authentication dialog. If you are using the service account flow there is no need for this. In fact, once you have run this once for you script you won't need to run it again.
 

The OAuth2 flow involves this  simplified process
  • The app asks for an authentication code
  • This authentication code is returned to a redirect uri which has been previously agreed and set up in the App dashboard, between the service provider and you.
  • This authentication code is exchanged for an access token, which is then used to authorize your access a protected resource until it expires.
  • A refresh token can optionally be returned along with the access token. This refresh token can then be used to get a new access token without the need for an interactive dialog in the future.
The difficulty with this process is in handling the redirect uri. The OAuth2 provider calls back to this redirect uri which needs to be ready and able to respond by sending back the authentication code in return for an access token. In Apps script, this works like this.
  • When the request for an authentication code is made, a state token is also sent. This state token includes various information which identifies which Apps Script function to call when a call back to the redirect uri is detected. Since the callback also returns the provided state token, Apps Script can call the given function. 
  • When this function is called, it is in a new context. This means that anything your script has done to this point is forgotten. It is this redirection and restart that cause most people problems when using OAuth2 at first. 
Goa tries to hide all that complication by avoiding a dialog if an unexpired access token exists, or can be refreshed. If a dialog is needed, then it takes care of the redirect callbacks and preserves arguments across calls. 

The pattern

Let's say you have a webapp that uses the Google DataStore, and needs a token to access it. You want to write the app to function the same way whether or not an authentication dialog has had to take place. 
function doGet(e) {
    return doGetDataStore (e);
}

The doGetDataStore function needs to either reuse, refresh or create an access token before it can access the data store. 

First step is to create a Goa which accesses the credentials for the DataStore, and then execute it.
var goa = cGoa.GoaApp.createGoa ('DriverDatastore_example',PropertiesService.getScriptProperties()).execute(e);

What does execute do?

It depends on what token work is required. One of these 3 things will happen.
  • If it detects there is an access token available with some time left , it will do nothing.
  • If there is no available access token, but  there is a refresh token, it will refresh the access token and store it for future use.
  • If there is no refresh token, then it needs to prepare for a fresh consent dialog.

Getting consent

If a consent dialog is needed, then your script needs to return this consent dialog to doGet for display. This is all that's required for that.
  if (goa.needsConsent()) {
    return goa.getConsent();
  }

Goa will have arranged to be called back to the same function it was executed from. This means that after the consent is received and a token generated, it will pass through exactly the same code as before.

var goa = cGoa.GoaApp.createGoa ('DriverDatastore_example',PropertiesService.getScriptProperties()).execute(e);

  if (goa.needsConsent()) {
    return goa.getConsent();
  }

Except this time, 
  • goa.execute will detect that there is now an available access token and do nothing. 
  • goa.needsConsent will be false and, the flow will continue on. 
The major benefit of this approach is that there is no need for a separate flow or special handling when a consent dialog is needed. 

Getting the token

Once the consent test has passed, there should always be a token available. It can be tested like this. 

  if (!goa.hasToken()) throw 'something went wrong with goa - did you check if consent was needed?';

Now the app can continue- in this case doing a query on the datastore and passing the token as well as any parameters that had originally been passed to the doGet() function.

  var result = testDataStore (goa.getToken(), goa.getParams() );   
  
  // now return it as normal
  return HtmlService.createHtmlOutput (result.getContentText())
    .setSandboxMode(HtmlService.SandboxMode.IFRAME);

Parameters

If there has been a consent dialog, then the parameters (e)  passed to doGet when it was called second time will not be available, since as far as Apps Script is concerned, this is a completely new invocation. 

When Goa detects that a consent dialog is needed, it stores the original parameters for later use. goa.getParams() will retrieve them. 

The final pattern

Here's the whole thing. 

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);

}

The redirect Uri

For convenience, Goa shows the redirect Uri it needs to be put into the App Dashboard, as in the example below. Just copy and paste it into the app dashboard or developer console if you are just setting the app up for the first time. 


Sidebars and Dialogs

The example showed how to handle a consent dialog in a webapp. If you are using a container bound script, you may prefer to render it in a sidebar or dialog (although I recommend you just do a simple one off doGet() and pick up the refreshed token without dialog thereafter). Since goa.getConsent() simply returns HtmlOutput, it can be rendered as follows if you want to. 

function sidebarDataStore (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 in the sidebar
  if (goa.needsConsent()) {
  
    var html = goa.getConsent()
        .setTitle('oauth2 dialog for ' + goa.getPackage().packageName)
        .setWidth(300);
    

    SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
      .showSidebar(html);
  }
}


Here's the dialog in a sidebar.

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

You want to learn Google Apps Script?

Learning Apps Script, (and transitioning from VBA) are covered comprehensively in my my book, Going Gas - from VBA to Apps script, All formats are available from O'ReillyAmazon and all good bookshops. You can also read a preview on O'Reilly

If you prefer Video style learning I also have two courses available. also published by O'Reilly.
Google Apps Script for Developers and Google Apps Script for Beginners.


Comments