rramona2
2/25/2018 - 11:33 AM

This is a Google Apps Script (or Adwords Scripty) set of Oauth 2.0 Functions. The idea is that you can use this to start the OAuth 2.0 flow

This is a Google Apps Script (or Adwords Scripty) set of Oauth 2.0 Functions. The idea is that you can use this to start the OAuth 2.0 flow and to get tokens, using only Google App Scripts and Drive (to store the tokens).

//  Script for handling OAuth2.0 with AdWords Scripty
//  Uses Google Drive as a database to store tokens 

/*
  Set the script variables below, using authentication for a web client from google project console
  Create two empty text files on Drive, get their file IDs and enter as variables below
  Follow the steps in firstRun function, starting with step 1
  
  For the uninitiated, the flow for OAuth2.0 is as follows:
  1) Generate the URL to request access from Google
  2) Go to the URL.  Accept the access request.
  3) Get redirected to authorized redirect URL (that you set in project console).
  4) Get the "code=" URL parameter from redirect URL.
  5) Use the code to request access token, and store token to drive.
  6) Requested token comes with seperate refresh token, which is saved as well.
  7) Refresh token can be used to get new access tokens when the old access tokens get too old.
*/

//  Whenever you need to get the access token in the future, just run tokenize() to get the most recent token
//    var accessToken = tokenize().access_token;

//  Uses these access tokens in the URL as parameters to access Google APIs.
//  For example, to access Cloud Storage:
//    var result = UrlFetchApp('https://www.googleapis.com/upload/storage/v1beta2/b/bucketName/o?uploadType=resumable&access_token='+accessToken,options);

 //  Oforce Cerp OAuth2.0 Parameters.  Redirect URL can be changed but needs to be set in the Google console
 //  These are web client authentication credentials.  Will require a one-time setup at the beginning [firstRun()]
var AUTHORIZE_URL = 'https://accounts.google.com/o/oauth2/auth'; 
var TOKEN_URL = 'https://accounts.google.com/o/oauth2/token'; 
var CLIENT_ID = 'somethingsomethingsomething.apps.googleusercontent.com';
var CLIENT_SECRET = 'ClientSecretSHHHHH';
var REDIRECT_URL = 'http://Whatever.redirect.url/you/set/in/the/console/';
//  Define the scope of access.  Add as many as needed.  Here we have Cloud Storage, Big Query, and basic profile.
var SCOPE = [
  'https://www.googleapis.com/auth/devstorage.full_control',
  'https://www.googleapis.com/auth/bigquery',
  'https://www.googleapis.com/auth/userinfo.profile&state=/profile'
  ]



//  Drive File IDs for Tokens
//  Create two blank text files.  Get the IDs for the text files by opening them in drive in preview mode
//  and then clicking on the expand button in the upper right hand side.  The file ID will be in the URL.
var accessTokenFileId = 'SomeFileId';  //  File Ids look similar to '0B7ufjVHAYwA9MVFUZ0RJk7JNYkk' 
var refreshTokenFileId = 'SomeFileId';

//  Steps to create a new access and refresh token.  Only do if the Drive token files are deleted or this is a first run.
//  Uncomment the Logger.log steps below one at a time.  Once you successfully run step 2, use tokenize() to get token.
//  The tokens will automatically refresh on their own in the future, so hopefully you will only do this firstRun() once.
function firstRun(){
  //  Step 0:
  //  Revoke older token if needed.  Will error out if the token is already revoked or doesn't exist.
  //Logger.log(revokeToken());

  //  Step 1:
  //Logger.log(getURLForAuthorization());
  //  Go to the logged URL and approve access.  
  //  Get a token request code in the redirect URL after you okay it.  Doesn't really matter where
  //  you are redirected.  Code looks like  &code=4/H8tu5Ll9Ei501qyQQJXu3mlPizIZ.EqEVHfEeCzQZMqTmHjyTFGP-TneligI
  //  The code is only usable once!  Run step 1 again if step 2 fails with a code.
  

  //  Step 2:
  //Logger.log(getAndStoreAccessToken('4/DA4bQlHOI79sYQkAc1HDFgBEJlBn.MkH9hjSEYFccmmS0T3UFEsPmpv7OigI'));
  //  Use the code from step 1 to request a token.  The token will then be saved to a Drive folder for later use.
  //  If you never touch the drive files, then you never have to worry about running the firstRun() again.
  
}

//  Grabs Access token
function getToken(){
  var tokenFile = DriveApp.getFileById(accessTokenFileId);
  var accessToken = tokenFile.getBlob();
  return JSON.parse(accessToken.getDataAsString());
}

//  Grabs Refresh token
function getRefreshToken(){
  var refreshToken = DriveApp.getFileById(refreshTokenFileId).getBlob().getDataAsString();
  return refreshToken;
}

//  Revokes Access for tokens.  Will need to be reissued by user.
function revokeToken(token){
  var token = token || tokenize().access_token;
  var response = UrlFetchApp.fetch('https://accounts.google.com/o/oauth2/revoke?token='+token).getContentText();
  return response;
}

//  Will check the existing token and refresh if old.  Returns most current token
//  This is the function you will use most for getting the access token.  If the access token is old
//  it will automatically refresh it and return the newest one.  Always run when pulling token.
function tokenize(){
  var token = getToken();
  var accessToken = token.access_token;
  var response = UrlFetchApp.fetch('https://www.googleapis.com/oauth2/v1/tokeninfo?access_token='+accessToken,{'muteHttpExceptions':true}).getContentText();
  var message = JSON.parse(response);
  if(message.error == "invalid_token"){   
    var token = refreshToken();
  }
  return token;
}

//  Will refresh the tokens using the special refresh token
function refreshToken(){
  var refreshToken = getRefreshToken();
  var parameters = {
    method : 'post',
    payload : 'client_id='+CLIENT_ID+'&client_secret='+CLIENT_SECRET+'&grant_type=refresh_token&refresh_token='+refreshToken
  };
  var response = UrlFetchApp.fetch(TOKEN_URL,parameters).getContentText();       
  
  //  Store the authorization token to drive
  try{
    var file = DriveApp.getFileById(accessTokenFileId);
    file.setContent(response);
  } catch(e) {   //  Recreate file, can't access
    throw("Token refresh problem!  Can't refresh a new Big Query Access Token! \n"+e);
  }
  return JSON.parse(response);  
}


//  Generate an Authorization URL.  A user will need to go to the URL in order to grant access.
//  After granting access, the landing page will return to a Scripty Script.  Check the url for the code= variable.
function getURLForAuthorization(){
  return AUTHORIZE_URL + '?response_type=code&client_id='+CLIENT_ID+'&access_type=offline&approval_prompt=force&redirect_uri='+REDIRECT_URL +
    '&scope='+SCOPE.join(" ");  
}


//  Using the URL parameter 'code', granted from the Authorization URL above, we can generate and save a new token
function getAndStoreAccessToken(code){
  var parameters = {
    method : 'post',
    payload : 'client_id='+CLIENT_ID+'&client_secret='+CLIENT_SECRET+'&grant_type=authorization_code&redirect_uri='+REDIRECT_URL+'&code=' + code
  };
  
  var response = UrlFetchApp.fetch(TOKEN_URL,parameters).getContentText();   
  var tokenResponse = JSON.parse(response);
  
  //  Store the authorization token to drive
  try{
    var file = DriveApp.getFileById(accessTokenFileId);
    file.setContent(response);
    var refresh = DriveApp.getFileById(refreshTokenFileId);
    refresh.setContent(tokenResponse.refresh_token)
  } catch(e) {   //  Recreate file, can't access
    throw("Token writing problem!  Can't create new OAuth 2.0 Access Token! \n"+e);
    //  Use to rewrite tokens to Drive if necessary:
    //  writeFileToDrive('BigQueryAccessToken',response,driveFolderID);
    //  writeFileToDrive('BigQueryRefreshToken',tokenResponse.refresh_token,driveFolderID);
  }
  return response;
}


//  Wrapper for writing a file to drive.  May not be needed if you already have Drive file IDs
//  Gets around some of the QPS errors that Drive has.
function writeFileToDrive(filename,file,folderId) {
  var hasWritten = false;
  var numAttempts = 0;
  var fileOut;
  while(!hasWritten && numAttempts < 6) {
    try {
      fileOut = DriveApp.getFolderById(folderId).createFile(filename,file); 
      hasWritten = true;
      Logger.log("Attempt number " + numAttempts + " was successful");
    } catch (e) {
      Logger.log("Attempt number " + numAttempts + " failed");
      if(numAttempts==5){throw ("Write Function Failed after 5 Attempts.  Error Message: \n"+e);}
      numAttempts++;
      Utilities.sleep(1000);
    }
  }
  return fileOut;
}