5

I am trying to use Google Scripts to create an add-on to use in a Google Spreadsheet. 

In the add-on I am connecting to third party APIs (Pocket APIs here) which require me to do OAuth. While using OAuth, I need to give a Redirect URI when authorisation API of Pocket is called. 

As per information on this link: https://github.com/gsuitedevs/apps-script-oauth2, the redirect URI in such cases is of the format https://script.google.com/macros/d/{SCRIPT ID}/usercallback. I used this format, but I am getting a "Unable to open file" kind of error. I used "OAuth2.getRedirectUri()" to get the Redirect URI.

Clarification on the comment that I got: I am not opening Redirect URI directly. I have specified the it in my call to Pocket API. Here's the complete code:

/**
 * @OnlyCurrentDoc  Limits the script to only accessing the current spreadsheet.
 */

var API_KEY = '87572-369f6490c104433f539c40d6';
var FIRST_TOKEN = '';

/**
 * Adds a custom menu with items to show the sidebar and dialog.
 *
 * @param {Object} e The event parameter for a simple onOpen trigger.
 */
function onOpen(e) {
  SpreadsheetApp.getUi()
      .createAddonMenu()
      .addItem('Show articles', 'getPocketArticles')
      .addToUi();
}

/**
 * Runs when the add-on is installed; calls onOpen() to ensure menu creation and
 * any other initializion work is done immediately.
 *
 * @param {Object} e The event parameter for a simple onInstall trigger.
 */
function onInstall(e) {
  onOpen(e);
}

/*

*/

function getPocketArticles() {
  // URL and params for the Pocket API
  var root = 'https://getpocket.com/v3/';
  var pocketService = getPocketService();
  getPocketToken(root);
}

function getPocketToken(root) {

  var url = root + 'oauth/request';

  var payloadData = {
      'consumer_key': API_KEY,
      'redirect_uri': OAuth2.getRedirectUri(),
      'state': ScriptApp.newStateToken()
                      .withMethod(usercallback)
                      .withTimeout(3600)
                      .createToken()
    };

  // parameters for url fetch
  var params = {
    'method': 'POST',
    'contentType': 'application/json; charset=UTF8',
    'headers': {
       'X-Accept' : 'application/json'
    },
    'payload': JSON.stringify(payloadData)
  };

  Logger.log(params);

  // call the Pocket API
  var response = UrlFetchApp.fetch(url, params);
  var data = response.getContentText();
  var json = JSON.parse(data);
  FIRST_TOKEN = json['code'];
  Logger.log("First token: "+FIRST_TOKEN);

  showSidebar(root);
}


/*
 * Register the Pocket Service
*/

function getPocketService() {
  // Create a new service with the given name. The name will be used when
  // persisting the authorized token, so ensure it is unique within the
  // scope of the property store.
  return OAuth2.createService('pocket')

      // Set the endpoint URLs.
      //.setAuthorizationBaseUrl('https://getpocket.com/auth/authorize')
      .setAuthorizationBaseUrl('https://getpocket.com/auth/authorize?request_token='+FIRST_TOKEN)

      /*.setTokenUrl('')*/

      // Set the client ID and secret, from the Google Developers Console.
      .setClientId(API_KEY)
      .setClientSecret(FIRST_TOKEN)

      // Set the name of the callback function in the script referenced
      // above that should be invoked to complete the OAuth flow.
      .setCallbackFunction('usercallback')

      // Set the property store where authorized tokens should be persisted.
      .setPropertyStore(PropertiesService.getUserProperties())

      // Set the scopes to request (space-separated for Google services).
      /*.setScope('')*/

      // Below are Google-specific OAuth2 parameters.

      // Sets the login hint, which will prevent the account chooser screen
      // from being shown to users logged in with multiple accounts.
      //.setParam('login_hint', Session.getActiveUser().getEmail())

      // Requests offline access.
      //.setParam('access_type', 'offline')

      // Forces the approval prompt every time. This is useful for testing,
      // but not desirable in a production application.
      //.setParam('approval_prompt', 'force');

     .setTokenHeaders({
      /*'Authorization': 'Basic ' +
          Utilities.base64Encode(CLIENT_ID + ':' + CLIENT_SECRET),*/
       'request_token': FIRST_TOKEN,
       'X-Accept' : 'application/json'
      })
  /*
    // Avoid "invalid_client error".
    // This service does not support form field authentication.
    .setTokenPayloadHandler(function(tokenPayload) {
      delete tokenPayload.client_id;
      return tokenPayload;
    })*/

     // Setting Payload 
     .setTokenPayloadHandler(function(tokenPayload) {
      tokenPayload.state = ScriptApp.newStateToken()
                      .withMethod(usercallback)
                      .withTimeout(3600)
                      .createToken();
      return tokenPayload;
    })

  ;


}

/**
 * Opens a sidebar. The sidebar structure is described in the Sidebar.html
 * project file.
 */
function showSidebar(root) {  
  var pocketService = getPocketService();
  var authorizationUrl = pocketService.getAuthorizationUrl();
  Logger.log("Authorization URL: " + authorizationUrl);

  if (!pocketService.hasAccess()) {
    var template = HtmlService.createTemplate(
        '<a href="<?= authorizationUrl ?>" target="_blank">Authorize</a>. ' +
        'Reopen the sidebar when the authorization is complete.');
    template.authorizationUrl = authorizationUrl;
    var page = template.evaluate();
    SpreadsheetApp.getUi().showSidebar(page);
  } else {
  // ...
  getPermanentToken(root);
  }


}


/**
 * 
 */

function getPermanentToken(root) {

  var url = root + 'oauth/authorize';

  var payloadData = {
      'consumer_key': API_KEY,
      'code': FIRST_TOKEN
    };

  // parameters for url fetch
  var params = {
    'method': 'POST',
    'muteHttpExceptions': false,
    'contentType': 'application/json; charset=UTF8',
    'headers': {
       'X-Accept' : 'application/json'
    },
    'payload': JSON.stringify(payloadData)
  };

  /*'contentType': 'application/json',*/

  // call the Pocket API
  Logger.log("URL: "+url+"\nparams: "+JSON.stringify(params));
  var response = UrlFetchApp.fetch(url, params);
  Logger.log("Response: "+response.getAllHeaders());

}

/**
 * Handle Callback 
 * 
 */

function usercallback(request) {
  var pocketService = getPocketService();
  var isAuthorized = pocketService.handleCallback(request);
  if (isAuthorized) {
    return HtmlService.createHtmlOutput('Success! You can close this tab.');
  } else {
    return HtmlService.createHtmlOutput('Denied. You can close this tab');
  }
}



I have done this on the basis of the guidelines given on github link shared above, and the examples mentioned on https://github.com/gsuitedevs/apps-script-oauth2/tree/master/samples. These examples are not exactly applicable to me as Pocket's APIs seem to behave bit differently (or so I understand)

I have put logger statement for Authorization URL too. It is shown in the logs I shared below.

Execution Transcript logs:

[19-09-16 07:36:45:329 IST] Starting execution
[19-09-16 07:36:45:342 IST] PropertiesService.getUserProperties() [0 seconds]
[19-09-16 07:36:45:342 IST] ScriptApp.getScriptId() [0 seconds]
[19-09-16 07:36:45:344 IST] Logger.log([{headers={X-Accept=application/json}, method=POST, payload={"consumer_key":"87572-369f6490c104433f539c40d6","redirect_uri":"https://script.google.com/macros/d/1QWlUgkmucy_wXK3v1BUjbxh9Ei1bBan7AFL2Jce...) [0 seconds]
[19-09-16 07:36:45:513 IST] UrlFetchApp.fetch([https://getpocket.com/v3/oauth/request, {headers={X-Accept=application/json}, method=POST, payload={"consumer_key":"87572-369f6490c104433f539c40d6","redirect_uri":"https://script.google.com/macros/d/...) [0.168 seconds]
[19-09-16 07:36:45:513 IST] HTTPResponse.getContentText() [0 seconds]
[19-09-16 07:36:45:514 IST] Logger.log([First token: 5c9102f2-3feb-caa6-86f0-c4530e, []]) [0 seconds]
[19-09-16 07:36:45:515 IST] PropertiesService.getUserProperties() [0 seconds]
[19-09-16 07:36:45:516 IST] ScriptApp.newStateToken() [0 seconds]
[19-09-16 07:36:45:516 IST] StateTokenBuilder.withMethod([usercallback]) [0 seconds]
[19-09-16 07:36:45:517 IST] StateTokenBuilder.withArgument([serviceName, pocket]) [0 seconds]
[19-09-16 07:36:45:517 IST] StateTokenBuilder.withTimeout([3600]) [0 seconds]
[19-09-16 07:36:45:517 IST] ScriptApp.getScriptId() [0 seconds]
[19-09-16 07:36:45:517 IST] StateTokenBuilder.createToken() [0 seconds]
[19-09-16 07:36:45:518 IST] Logger.log([Authorization URL: https://getpocket.com/auth/authorize?request_token=5c9102f2-3feb-caa6-86f0-c4530e&client_id=87572-369f6490c104433f539c40d6&response_type=code&redirect_uri=https%3A%2F%2Fscript.goog...) [0 seconds]
[19-09-16 07:36:45:523 IST] Properties.getProperty([oauth2.pocket]) [0.005 seconds]
[19-09-16 07:36:45:525 IST] HtmlService.createTemplate([<a href="<?= authorizationUrl ?>" target="_blank">Authorize</a>. Reopen the sidebar when the authorization is complete.]) [0.001 seconds]
[19-09-16 07:36:45:525 IST] Function.apply([[]]) [0 seconds]
[19-09-16 07:36:45:526 IST] HtmlService.createHtmlOutput() [0 seconds]
[19-09-16 07:36:45:527 IST] HtmlOutput.append([<a href="]) [0 seconds]
[19-09-16 07:36:45:528 IST] HtmlOutput.appendUntrusted([https://getpocket.com/auth/authorize?request_token=5c9102f2-3feb-caa6-86f0-c4530e&client_id=87572-369f6490c104433f539c40d6&response_type=code&redirect_uri=https%3A%2F%2Fscript.google.com%2Fmacros%2Fd...) [0 seconds]
[19-09-16 07:36:45:528 IST] HtmlOutput.append([" target="_blank">Authorize</a>. Reopen the sidebar when the authorization is complete.]) [0 seconds]
[19-09-16 07:36:45:529 IST] HtmlOutput.append([]) [0 seconds]
[19-09-16 07:36:45:530 IST] SpreadsheetApp.getUi() [0 seconds]
[19-09-16 07:36:45:595 IST] Ui.showSidebar([HtmlOutput]) [0.064 seconds]
[19-09-16 07:36:45:658 IST] Execution succeeded [0.255 seconds total runtime]

Output of the logger statements that I have in my code:

[19-09-16 07:36:45:343 IST] {headers={X-Accept=application/json}, method=POST, payload={"consumer_key":"87572-369f6490c104433f539c40d6","redirect_uri":"https://script.google.com/macros/d/1QWlUgkmucy_wXK3v1BUjbxh9Ei1bBan7AFL2JceT2401-ztkEtFk9-xb/usercallback"}, contentType=application/json; charset=UTF8}
[19-09-16 07:36:45:514 IST] First token: 5c9102f2-3feb-caa6-86f0-c4530e
[19-09-16 07:36:45:518 IST] Authorization URL: https://getpocket.com/auth/authorize?request_token=5c9102f2-3feb-caa6-86f0-c4530e&client_id=87572-369f6490c104433f539c40d6&response_type=code&redirect_uri=https%3A%2F%2Fscript.google.com%2Fmacros%2Fd%2F1QWlUgkmucy_wXK3v1BUjbxh9Ei1bBan7AFL2JceT2401-ztkEtFk9-xb%2Fusercallback&state=ADEpC8z2oP8q4juC4cgceCqVZw34DEX3KTdN9Cm5fPX-Nh7vzYIDdw50GIHNMfQ--Y92uqA_K8RUaAaHf7OU9O72RPOQ3ryYBVTrlQ-ZLZRQRgJ5Re68KOTud8ckAnonjG24a5-W2ti7g3o5rQebaDnhIlTLjY2MJWrP68pf70FSak6nhby7B_quV6PCmIjbCfS0R54D6oV3tTCwrL9JpO62zxmIHLkceD0O-cZc8SUrJK1yMDBcofuZCqGIxjlBOVpnCvugSnhCczp3qCaEA-3cLj3jwzXDX4XluqX7c-0hWsrkgHQNFxiB7qFo7pzhd8NlBcVj4t6yoQyTfquYN84C1wGKxcjtfg

What happens is that when I invoke this URL I get redirected to Pocket authorisation page that asks me to authorise this add-on to have permission to access my Pocket data. That is fine, but when I click on Authorize, I get "Unable to open file" kind of error. This is the screenshot of how it appears: https://photos.app.goo.gl/VVCcEfh6NYTWK6MW9

I wonder where I am going wrong. Could someone please guide me?

I am new to Google Scripts and OAuth, so please pardon if this is a silly question.

Mukesh Ghatiya
  • 398
  • 1
  • 5
  • 15
  • You can't open redirect uri directly(without state token). When pocket api redirects it'll automatically run the callback function in the state token. – TheMaster Sep 15 '19 at 07:17
  • Thanks Master for the reply. I am not calling Redirect URI directly. I have now updated my original question and given some code sample. Mind checking that? – Mukesh Ghatiya Sep 15 '19 at 13:11
  • 1. Provide complete script. See [mre]. 2. Seeing your photo, It seems `state` token is not passed back in the url. Try including `state` in ``payloadData``(Create using `ScriptApp.newStateToken()....`). 3. Provide detail network logs as seen in the dev console. i.e., list of GET, POST requests made and redirected urls. This is needed to verify if state token is indeed passed back. – TheMaster Sep 15 '19 at 15:50
  • 4. `X-Accept` must be inside `headers` not inside `params` – TheMaster Sep 15 '19 at 16:18
  • Thanks Master. I have updated the post with full code and the logs. I have changed the X-Accept to be part of header too. That has helped as I am now getting JSON reply which I was not getting earlier. Thanks. – Mukesh Ghatiya Sep 16 '19 at 02:22
  • Regarding ```state``` token, I see that when I log Redirect URI, it does have a ```state``` parameter. Does that confirm that state token is being formed? Or do I still need to explicitly set it? – Mukesh Ghatiya Sep 16 '19 at 02:22
  • I meant network logs in the browser developer console. If you click auth url, a GET request is made from the browser. This is logged in the console. `state` token is formed but after clicking authorize in the pocket, pocket should send back the state token as a query parameter `redirecturi?state=...&accesstoken=....` – TheMaster Sep 16 '19 at 07:35
  • Try including ``state`` in ``payloadData``. If that doesn't make pocket send back the state, I think nothing will. You can get the state from the authorization url. But, if user authorizes, you can still try getting the access token, even if there was a error on redirect url page. Also remove client id, secret. They are not required by this api – TheMaster Sep 16 '19 at 09:42
  • Thanks for the replies. I see that OAuth library that I am using does add state as part of getAuthorizationUrl in https://github.com/gsuitedevs/apps-script-oauth2/blob/master/dist/OAuth2.gs – Mukesh Ghatiya Sep 16 '19 at 17:51
  • However, I anyway added the state to the payloadData as you suggested. I assumed you meant to put in the function getPocketToken() in my code and not in the getPermanentToken(). However, it hasn't helped. Here are the Dev Console Logs: https://drive.google.com/open?id=1hyUlkWM_f5TbGHGjM1yMpwJ-bGTa2SRO – Mukesh Ghatiya Sep 16 '19 at 17:51
  • Removing Client ID and Secret caused an error saying "Client ID is needed" – Mukesh Ghatiya Sep 16 '19 at 17:52
  • *However, I anyway added the state to the payloadData as you suggested. I assumed you meant to put in the function getPocket*. I meant getPocketService. May I know how you added the state token here? Could you [edit] to show the latest script? – TheMaster Sep 16 '19 at 20:18
  • Btw, you should be able to getPermanentToken, even if redirect failed. Try running `getPermanentToken` and see? – TheMaster Sep 16 '19 at 20:24
  • I have updated the code in original post to reflect the way I added payloadData in getPocketToken function. I am yet to figure out how to add payload in getPocketService function. I'll update you once I do that. – Mukesh Ghatiya Sep 17 '19 at 02:13
  • Earlier I had also understood the same that I should be able to get permanent token even if redirect failed. However, that had been giving me 403 error. – Mukesh Ghatiya Sep 17 '19 at 02:15
  • I now edited getPocketService to have state too in the payload. Hope I have done it correctly. I have updated the original code to reflect that. Here's the network log when I ran the latest code: https://drive.google.com/open?id=1m_Oq7XIMgecwVEb7hLLQx6r6zr6O7eVj – Mukesh Ghatiya Sep 17 '19 at 02:25
  • I currently don't have time. But I suggest 1. Skip the oauth library. Do everything yourself through urlferchapp and properties service 2. Publish a web-app from the script. You'll get /exec url. You can use that as redirect which won't backfire. PS: After skimming your script, `state` will be different in the url created by oauth and yours. – TheMaster Sep 17 '19 at 19:37
  • 1
    Thanks for your time so far. I'll try and let you know how it goes. – Mukesh Ghatiya Sep 18 '19 at 16:00

0 Answers0