Pass an extra auth= arg to the data url if we have one.

If we do, it'll be in localStorage[['auth', urlhost]].

You can set it by calling the /setauth URL, which happens to work as a
GoogleAPI-oauth2-compatible callback (but can be used for any API you want
as long as it provides the right query parameters).
diff --git a/app.yaml b/app.yaml
index aeb616e..ef69491 100644
--- a/app.yaml
+++ b/app.yaml
@@ -11,6 +11,9 @@
 - url: /edit
   static_files: edit.html
   upload: edit.html
+- url: /(setauth|oauth2callback)
+  static_files: setauth.html
+  upload: setauth.html
 - url: /help
   static_files: help.html
   upload: help.html
diff --git a/render.js b/render.js
index b0e9fc5..0defc6d 100644
--- a/render.js
+++ b/render.js
@@ -2,7 +2,7 @@
 
 var afterquery = (function() {
   // To appease v8shell
-  var console;
+  var console, localStorage;
   try {
     console = window.console;
   }
@@ -11,6 +11,11 @@
       debug: print
     };
   }
+  try {
+    localStorage = window.localStorage;
+  } catch (ReferenceError) {
+    localStorage = {}
+  }
 
   // For konqueror compatibility
   if (!console) {
@@ -1079,9 +1084,28 @@
   }
 
 
+  var URL_RE = RegExp("^((\\w+:)?(//[^/]*)?)");
+
+
+  function urlMinusPath(url) {
+    var g = URL_RE.exec(url);
+    if (g && g[1]) {
+      return g[1];
+    } else {
+      return url;
+    }
+  }
+
+
   function getUrlData(url, success_func, error_func) {
     // some services expect callback=, some expect jsonp=, so supply both
     var plus = 'callback=jsonp&jsonp=jsonp';
+    var hostpart = urlMinusPath(url);
+    var auth = localStorage[['auth', hostpart]];
+    if (auth) {
+      plus += '&auth=' + encodeURIComponent(auth);
+    }
+
     var nurl;
     if (url.indexOf('?') >= 0) {
       nurl = url + '&' + plus;
@@ -1109,14 +1133,16 @@
       error(null, message + ' url=' + xurl + ' line=' + lineno);
     };
 
-    iframe.contentWindow.loaded = function() {
-      alert('loaded');
-    };
-
-    iframe.contentDocument.write(
-        '<script async onerror="loaded" onload="loaded" src="' +
-        encodeURI(nurl) +
-        '"></script>');
+    //TODO(apenwarr): change the domain/origin attribute of the iframe.
+    //  That way the script won't be able to affect us, no matter how badly
+    //  behaved it might be.  That's important so they can't access our
+    //  localStorage, set cookies, etc.  We can use the new html5 postMessage
+    //  feature to safely send json data from the iframe back to us.
+    // ...but for the moment we have to trust the data provider.
+    var script = document.createElement('script');
+    script.async = 1;
+    script.src = nurl;
+    iframe.contentDocument.body.appendChild(script);
   }
 
 
@@ -1147,6 +1173,7 @@
       orderBy: orderBy,
       extractRegexp: extractRegexp,
       fillNullsWithZero: fillNullsWithZero,
+      urlMinusPath: urlMinusPath,
       gridFromData: gridFromData
     },
     render: wrap(_run)
diff --git a/setauth.html b/setauth.html
new file mode 100644
index 0000000..e6d5737
--- /dev/null
+++ b/setauth.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<html>
+<body>
+
+<script src="render.js"></script>
+<script>
+
+var args = afterquery.internal.parseArgs(
+    (window.location.search || '?') + '&' + window.location.hash.substr(1));
+
+var token = args.get('access_token') || args.get('auth');
+var url = args.get('state') || args.get('url');
+var hostpart = url ? afterquery.internal.urlMinusPath(url) : null;
+
+if (token && hostpart) {
+  localStorage[['auth', hostpart]] = token;
+}
+
+</script>
+
+</body>
+</html>
diff --git a/t/trender.js b/t/trender.js
index 40a8ead..f8cc05d 100644
--- a/t/trender.js
+++ b/t/trender.js
@@ -113,3 +113,17 @@
   WVPASSEQ(guessTypes(data2.concat(datanull)),
            ['date', 'datetime', 'number', 'string', 'boolean', 'boolean']);
 });
+
+
+wvtest('urlMinusPath', function() {
+  WVPASSEQ(afterquery.internal.urlMinusPath('http://x/y/z'),
+           'http://x');
+  WVPASSEQ(afterquery.internal.urlMinusPath('https://u:p@host:port/y/z'),
+           'https://u:p@host:port');
+  WVPASSEQ(afterquery.internal.urlMinusPath('http:foo/blah//whatever'),
+           'http:');
+  WVPASSEQ(afterquery.internal.urlMinusPath('foo/blah//whatever'),
+           'foo/blah//whatever');
+  WVPASSEQ(afterquery.internal.urlMinusPath('//foo/blah//whatever'),
+           '//foo');
+});