title: "Authorization is required to perform that action" in Google Scripts with no way to give authorization
body: <p>I am trying to create a sort of middleware so some legacy software I am working on can consume Twitter feeds. </p>
  
  <p>Since Twitter has made API 1.0 obsolete and 1.1 requires OAuth and because for this project I only have client-side scripting available to me, I opted to use a Google script to perform the OAuth negotiation:</p>
  
  <p>Source: <a href="http://www.labnol.org/internet/twitter-rss-feeds/27931/" rel="nofollow">http://www.labnol.org/internet/twitter-rss-feeds/27931/</a></p>
  
  <pre><code>function start() {
      // Get your Twitter keys from dev.twitter.com
      var CONSUMER_KEY = "-----";
      var CONSUMER_SECRET = "----";
  
    // Ignore everything after this line
      initialize(CONSUMER_KEY, CONSUMER_SECRET);
  }
  
  
  
  function initialize(key, secret) {
  
      ScriptProperties.setProperty("TWITTER_CONSUMER_KEY", key);
      ScriptProperties.setProperty("TWITTER_CONSUMER_SECRET", secret);
  
      var url = ScriptApp.getService().getUrl();
      if (url) {
          connectTwitter();
          var msg = "";
  
          msg += "Sample RSS Feeds for Twitter\n";
          msg += "============================";
          msg += "\n\nTwitter Timeline of user @labnol";
          msg += "\n" + url + "?action=timeline&amp;q=labnol";
          msg += "\n\nTwitter Favorites of user @labnol";
          msg += "\n" + url + "?action=favorites&amp;q=labnol";
          msg += "\n\nTwitter List labnol/friends-in-india";
          msg += "\n" + url + "?action=list&amp;q=labnol/friends-in-india";
          msg += "\n\nTwitter Search for New York";
          msg += "\n" + url + "?action=search&amp;q=new+york";
          msg += "\n\nYou should replace the value of 'q' parameter in the URLs as per requirement.";
          msg += "\n\nFor help, please refer to http://www.labnol.org/?p=27931";
  
          MailApp.sendEmail(Session.getActiveUser().getEmail(), "Twitter RSS Feeds", msg);
  
      }
  
  }
  
  function doGet(e) {
  
      var a = e.parameter.action;
      var q = e.parameter.q;
      var feed;
  
    switch (a) {
      case "timeline":
          feed = "https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name=" + q;
          break;
      case "search":
          feed = "https://api.twitter.com/1.1/search/tweets.json?q=" + encodeString(q);
          break;
      case "favorites":
          feed = "https://api.twitter.com/1.1/favorites/list.json?screen_name=" + q;
          break;
      case "list":
          var i = q.split("/");
          feed = "https://api.twitter.com/1.1/lists/statuses.json?slug=" + i[1] + "&amp;owner_screen_name=" + i[0];
          break;
      default:
          feed = "https://api.twitter.com/1.1/statuses/user_timeline.json";
          break;
      }
  
      var id = Utilities.base64Encode(feed);
      var cache = CacheService.getPublicCache();
      var rss = cache.get(id);
  
      if ((!rss) || (rss == "undefined")) {
          rss = JSONtoRSS(feed, a, q);
          cache.put(id, rss, 3600);
      }
  
      return ContentService.createTextOutput(rss)
          .setMimeType(ContentService.MimeType.RSS);
  }
  
  
  function JSONtoRSS(json, type, key) {
      oAuth();
      var options = {
          "method": "get",
          "oAuthServiceName": "twitter",
          "oAuthUseToken": "always"
      };
  
    try {
          var result = UrlFetchApp.fetch(json, options);
          if (result.getResponseCode() === 200) {
              var tweets = Utilities.jsonParse(result.getContentText());
  
            if (type == "search")
                  tweets = tweets.statuses;
  
            if (tweets) {
                  var len = tweets.length;
                  var rss = "";
  
                if (len) {
  
                      rss = '&lt;?xml version="1.0"?&gt;&lt;rss version="2.0"&gt;';
                      rss += ' &lt;channel&gt;&lt;title&gt;Twitter ' + type + ': ' + key + '&lt;/title&gt;';
                      rss += ' &lt;link&gt;' + htmlentities(json) + '&lt;/link&gt;';
                      rss += ' &lt;pubDate&gt;' + new Date() + '&lt;/pubDate&gt;';
  
                      for (var i = 0; i &lt; len; i++) {
  
                          var sender = tweets[i].user.screen_name;
                          var tweet = htmlentities(tweets[i].text);
  
                          rss += "&lt;item&gt;&lt;title&gt;" + sender + ": " + tweet + "&lt;/title&gt;";
                          rss += " &lt;author&gt;" + tweets[i].user.name + " (@" + sender + ")&lt;/author&gt;";
                          rss += " &lt;pubDate&gt;" + tweets[i].created_at + "&lt;/pubDate&gt;";
                          rss += " &lt;guid isPermaLink='false'&gt;" + tweets[i].id_str + "&lt;/guid&gt;";
                          rss += " &lt;link&gt;https://twitter.com/" + sender + "/statuses/" + tweets[i].id_str + "&lt;/link&gt;";
                          rss += " &lt;description&gt;" + tweet + "&lt;/description&gt;";
                          rss += "&lt;/item&gt;";
  
                      }
  
                      rss += "&lt;/channel&gt;&lt;/rss&gt;";
  
                      return rss;
  
                  }
  
              }
  
          }
  
      } catch (e) {
          Logger.log(e.toString());
      }
  
  }
  
  function connectTwitter() {
  
      oAuth();
  
      var search = "https://api.twitter.com/1.1/application/rate_limit_status.json";
  
      var options = {
          "method": "get",
          "oAuthServiceName": "twitter",
          "oAuthUseToken": "always"
      };
  
      try {
  
          var result = UrlFetchApp.fetch(search, options);
  
      } catch (e) {
          Logger.log(e.toString());
      }
  }
  
  
  function encodeString(q) {
  
      var str = encodeURIComponent(q);
      str = str.replace(/!/g, '%21');
      str = str.replace(/\*/g, '%2A');
      str = str.replace(/\(/g, '%28');
      str = str.replace(/\)/g, '%29');
      str = str.replace(/'/g, '%27');
      return str;
  
  }
  
  function htmlentities(str) {
  
      str = str.replace(/&amp;/g, "&amp;amp;");
      str = str.replace(/&gt;/g, "&amp;gt;");
      str = str.replace(/&lt;/g, "&amp;lt;");
      str = str.replace(/"/g, "&amp;quot;");
      str = str.replace(/'/g, "&amp;#039;");
      return str;
  
  }
  
  function oAuth() {
  
      var oauthConfig = UrlFetchApp.addOAuthService("twitter");
      oauthConfig.setAccessTokenUrl("https://api.twitter.com/oauth/access_token");
      oauthConfig.setRequestTokenUrl("https://api.twitter.com/oauth/request_token");
      oauthConfig.setAuthorizationUrl("https://api.twitter.com/oauth/authorize");
      oauthConfig.setConsumerKey(ScriptProperties.getProperty("TWITTER_CONSUMER_KEY"));
      oauthConfig.setConsumerSecret(ScriptProperties.getProperty("TWITTER_CONSUMER_SECRET"));
  
  }
  </code></pre>
  
  <p>I have followed all of the instructions prescribed in the guide including running 'start' twice... and all that does is send my email account an email with the various URLs. When I try to access the URLs provided in the email (and the plan is to drop that URL into our legacy javascript that currently points to the Twitter 1.0 API), i get the error "Authorization is required to perform that action"</p>
  
  <p>I have confirmed countless times that it is set up to "Execute the app as [me]" and "Anyone, even anonymous can access app"</p>
  
  <p>I am not sure what I am missing or what got screwed up. </p>