Digest authentication and Google Apps Script

The other day I was looking around for an example of how to do digest authentication in Google Apps Script (or even plain javascript). I found plenty of theoretical discussions on it, but no actual examples. So here’s an implementation walkthrough. You can include it live in your Google Apps Script Project from the mcpher shared library  or just copy the code.

What is Digest Authentication

Working with GAS, you’ll probably be more familiar with oAuth2, which is supported well through the UrlFetchApp class. However some APIS still use Basic authentication, or the slightly more secure Digest Authentication. Here is a wikipedia write up of it, and here is the RFC describing Digest Authentication in detail.

Worked example

We’ll use the real estate exchange format, defined here , as an example.

Step 1. Initialize the workflow 

  // kick off workflow , expecting a 401
  this.initWorkflow = function () {
      this.danceStep1 = UrlFetchApp.fetch(this.url, 
                      { “method” : “GET”, “muteHttpExceptions”: true} );
      return this;
  };
Notes
  • muteHttpExceptions is (I think undocumented) option that prevents urlfetch from crashing out and returning a null response
  • this.danceStep1 WWW-Authenticate header looks like this, and a 401 status code is returned. What we need from this to construct the next request is the nonce, qop, and realm from the WWW-Authenticate header. Other implementations also return a few other things like encoding algorithm, domain and the opaque value. 
  • It’s quite fiddly to parse the header- for some fields there are quotes – for others there are not. I wont go into the details of that here, but the parsing code is included in the code implementation.
    “WWW-Authenticate”: “Digest realm=\”CREA.Distribution\”, nonce=\”NjM1MDQ3NzA4NDAxNzguNDpjZWQ0N2U4ODI3ZGZhYmQ4ODRkZWJhZmM5ZjllYTYwYg==\”, qop=\”auth\””,
    

Step 2 – construct the digest

Using this code, we construct a digest header like this as per  Digest Authentication in detail.
      o.algorithm = “MD5”;
      var HA1 = bytesToHex(md5(this.credentials.username+’:’+o.realm+’:’+this.credentials.password));
      var HA2 = bytesToHex(md5(‘GET’+’:’+o.domain ));
      var response = bytesToHex(md5(HA1+’:’+o.nonce+”:” + nc+”:”+this.cnonce+”:”+o.qop+”:”+HA2));
  
      var digest = ‘Digest username=”‘ + this.credentials.username + ‘”‘ +
             ‘,realm=”‘ + o.realm + ‘”‘ +
             ‘,nonce=”‘ + o.nonce + ‘”‘ +
             ‘,uri=”‘ + o.domain + ‘”‘ +
             ‘,qop=’ + o.qop + 
             ‘,nc=’ + nc  +
             ‘,algorithm=’ + o.algorithm +
             ‘,cnonce=”‘ + this.cnonce + ‘”‘ + 
             ‘,response=”‘ + response + ‘”‘;

    // sometimes opaque not given
      if (o.opaque) digest += ‘,opaque=”‘ + o.opaque + ‘”‘;
Some of the intermediate values look like this, 
HA1 cfee3113c2f3ebf16863cddbc6bdcd33
HA2 e85bcbec0349f876e2ee5676ade2db50
RESPONSE a2c9f7eb682776be2ca152c4c3299d03
Step 3 – Finish the workflow
Now we can respond to that 401, this time we should get a 200 ok reponse
  this.finishWorkflow = function () {
      var options = 
       { “method” : “GET”, “muteHttpExceptions”: true , 
         “headers” : {
           “Authorization” : this.digest() 
         }
       }
      this.danceStep2 = UrlFetchApp.fetch(this.url, options);
      return this;
  }
Where this.digest() looks like this
{
    “method”: “GET”,
    “muteHttpExceptions”: true,
    “headers”: {
        “Authorization”: 
        “Digest username=\”CXLHfDVrziCfvwgCuL8nUahC\”,
          realm=\”CREA.Distribution\”,
          nonce=\”NjM1MDQ3NzA4NDAxNzguNDpjZWQ0N2U4ODI3ZGZhYmQ4ODRkZWJhZmM5ZjllYTYwYg==\”,
          uri=\”undefined\”,
          qop=auth,
          nc=00000001,
          algorithm=MD5,
          cnonce=\”13ec921de96\”,
          response=\”a2c9f7eb682776be2ca152c4c3299d03\””
    }
}

The Code

The digest auth code is accessible through the cDigestAuth object which you can include in your project from the mcpher shared library like this, or just copy the code from here.
Here’s the test 
//—Digest authentication in Google Apps Script
function digestTest() {
  var url = “http://sample.data.crea.ca/Login.svc/Login”;
  var creds = getRetsCredentials(“tester”);
  
  // do digest auth dance
  var d = new mcpher.cDigestAuth (url,creds).login();           
  // do something
  if (d.isLoggedIn()) {
     // do something .. we’re now logged in
  }
  else {
    Logger.log(“all is bad”);
    Logger.log (“failed to log in ” + JSON.stringify(d));
  }
}
your credentials should come from some secret place, for example scriptDB or script properties, and should return an object like this
     {username:”CXLHfDVrziCfvwgCuL8nUahC”,
       password:”mFqMsCSPdnb5WO1gpEEtDCHH”}
Here’s the complete code for cDigestAuth

http://xliberation.com/p/gaspubcontainer.html?source=script&module=cDigestAuth&library=mcpher

(function() { var po = document.createElement(‘script’); po.type = ‘text/javascript’; po.async = true; po.src = ‘https://apis.google.com/js/plusone.js’; var s = document.getElementsByTagName(‘script’)[0]; s.parentNode.insertBefore(po, s); })();

Author: bm082975

Leave a Reply

Your email address will not be published. Required fields are marked *