Saturday, January 26, 2008

Using Aptana Jaxer for search engine friendly pages?

I am interested in Aptana's Jaxer because they seemed to have gotten Gecko to work on the server. From what I have read before, the tricky part is getting Gecko to run without the need for a windowing service (like X-Windows). Maybe Jaxer still needs a windowing service, I have not looked that closely.

I am not so interested in using their runat="server" or server-proxy capabilities, but more interested in getting Jaxer to render my page to the final "onloaded" DOM that could then be sent to a search engine.

However, I have not seen an option yet to tell it "treat the whole page as runat server" or something like that.

Here is what I want to do (condensed from my previous post on searchable ajax):
  • Do the model/view/controller in HTML/CSS/JS. I normally want this all to run in the user's browser.
  • However, for search engine requests, I want to do some Apache rewrite/proxying to a server-side Gecko that would render the DOM as it would look after window.onload, then serialize that DOM as the result of the search engine's request.
I wonder if Jaxer could be used for the "server-side Gecko" part. If so, that would be sweet. That would allow my web app to be indexed by search engines properly, but still give me the development model I want. No special dev work to get search engine benefits.

Wednesday, January 23, 2008

X-Web-Epoch instead of X-UA-Compatible

This is in response to the X-UA-Compatible proposal from Microsoft to help IE8 and future IE browsers to know how to render web pages. There is a lot of commentary on the subject, but here are what I believe to be the original (public) sources for the proposal:
I understand and sympathize with IE's position: they need a switch to know when they can use a more standards-compliant rendering engine. There is a vast sea of font tags, tables, spacer images and document.all references inside corporate intranets, and no one is going to update that code, ever. However, any new versions of IE are expected to be able to render that sea of forgotten markup faithful to the original coder's intent. A responsibility IE gets for winning the first round of browser wars.

However, IE is getting push from today's web developers that want something easier to code against given today's suggested cultural approach to web development, namely adherence to publicly accepted web standards.

X-UA-Compatible provides a switch for IE to know what the developer wants: does the developer want the old wild west behavior, or a more standards-based behavior?

However, I do not like the X-UA-Compatible proposal because it focuses too much on specifying a rendering engine and engine version (like specifying IE=8). Actually it is even more specific than rendering engine, it is really a product version switch.

I think the switch should be more of a "culture version" switch than an explicit product version switch. Tying the switch to product versioning seems too fragile. Ideally, IE8 and IE9 will be released before there is a cultural shift in expectations over development.

It also makes it hard for me as a more standards-based developer to make a decision on X-UA-Compatible. I'll likely just use the "edge" value, but then over time, "edge" will lose its meaning and it will just mean "what web developers do today". We'll need a new switch in the future once "edge" reaches the status of today's DOCTYPE.

Maybe it makes more sense to use a culture version number instead. For the sake of argument, I'll call it X-Web-Epoch, but I am not tied to that name. The value for X-Web-Epoch would be an URL that corresponds to the epoch that is targeted. I see having two epochs right now:
  • Epoch 1: The tag soup, wild west version of the web IE is expected to render, in particular inside corporate intranets.
  • Epoch 2: The standards-based development of today. I would also include "the expectation that things develop and change over time" as part of that Epoch 2.
Epoch 1 is the default. It is used for web pages that do not have a X-Web-Epoch META tag or HTTP header.

So, something like this for Epoch 2:

<meta equiv="X-Web-Epoch" content="http://www.w3.org/epochs/2">

That www.w3.org URL does not actually exist, so spare the w3.org servers and do not try to actually go to it. This does not have to be the actual URL mentioned above, just some URL.

The URL should be resolvable to a real document that explains the cultural assumptions of that epoch.

The Epoch 2 document could list things like:
  • Assumes public standards-based development.
  • List the general set of standards.
  • Maybe list things like progressive enhancement, how to design for accessibility.
  • Most importantly: things will change over time as bugs are fixed to improve standards compliance. There also may be new standards adopted.
  • It could have a changelog since this document is allowed to change slightly over time.
The document would hopefully set expectations accordingly, in particular that things may change slightly over time. Bonus points: the document can serve as a pointer to how best to code in that Epoch.

Ideally the definition of Epoch 2 is big enough that we might have a chance to go many browser versions without needing a new Epoch, and browsers can go a long time without needing completely new rendering engines.

In reality, cultural expectations will change over time, and we will likely need an Epoch 3 to allow the browser market leader of that time to be able to implement a new rendering engine, so they do not have spaghetti code in just one giant backwards-compatible engine. But again, there should be a longer time span vs. what might be expected with X-UA-Compatible.

It also seems like a better approach than using "edge" in X-UA-Compatible.

This would give IE the switch it needs for its new rendering engine. Other browsers of today, they are just fine moving along with what they have now. They can ignore the switch for now, until they become market leader and have to adapt to an Epoch 3. Hopefully, web developers get iteration from IE in a more timely manner, and a better baseline for development.

Monday, December 03, 2007

Defining $ for Dojo and setting a function prototype

This is a little story about defining a $ function for Dojo, and a more fundamental discussion about setting a prototype for a function.

Defining a $ function for Dojo

I generally prefer using full namespace names when using JavaScript libraries. It helps avoid confusion when passing around code or copying/pasting code that may be used in mixed-library environments. However, if you have contained environment, you may prefer extreme brevity over namespace robustness.

So, how can we define a function named $ that works with the base Dojo functionality (the stuff you get in dojo.js: DOM querying, event handling, XHR, style, basic effects, JSON, array, language and object hierarchy helpers). Ideally, $ would be mapped to dojo.query, to allow easy node selection, but then any other dojo method, like dojo.connect() would be available via $.connect().

Here is something that works:

$ = function(){
return dojo.query.apply(dojo, arguments);
};
dojo.mixin($, dojo);


That does the basics: defines a function $ that returns the result of a dojo.query() call, then copies all the properties of dojo to the $ function object (via dojo.mixin()).

This allows for making these sorts of calls:

//Set on onclick listener for all divs in the body
$("div").onclick(function(){
alert("div clicked.");
});

//Do an XMLHttpRequest POST using data from
//a form with a DOM ID of "myFormId"
$.xhrPost({
form: $("#myFormId")[0]
});


Setting a prototype for a function

So that is nice if all I want is dojo base. However, Dojo has a neat module loader, and ideally I want to be able to do something like:

dojo.require("dojo.io.script");


Then later in the code just do a $.io.script.get(). However, this will not work if you define the $ function before doing the dojo.require() call. dojo.mixin() only copies over object properties that exist at the time of the dojo.mixin() call.

So, ideally, I want to set the prototype lookup for $ to be dojo. That way, as new things are added to the dojo object, they will be automatically available via $.

At this point I hit a wall, at least with my current understanding of JavaScript. I was hoping I could do something like this:

var Dollar = function(){
return function(){
return dojo.query.apply(dojo, arguments);
};
};
Dollar.prototype = dojo;

$ = new Dollar();


In JavaScript constructor functions, you do not have to return a value. If you do not, whatever the "this" value was inside the constructor function gets returned as the result of the call the "new" call.

However, if you return a value from the constructor, that is what is returned as the result of the "new" call. This is what Dollar does above.

What I was hoping would happen is that the returned function (that calls dojo.query()) would have its __proto__ property set to Dojo, but I think the __proto__ binding happens before the constructor function is called, so that you can access methods on "this" inside the constructor.

Suppose there was a constructor function called ConstructorFunction, and you called "new ConstructorFunction()". Here is some very crude psuedo-code for what I think the "new" call is doing:

var temp = {};
temp.__proto__ = ConstructorFunction.prototype;
return ConstructorFunction.apply(temp, arguments);


If this is what is going on, it makes sense that by returning a value from the constructor function call, I lose out on the __proto__ binding.

So why not just set the $.__proto__ value directly after $ is defined? Accessing __proto__ is strongly discouraged. It is an implementation's private implementation detail, subject to change. Also, doing that only works right now for Firefox 2 and Safari 3 (failed for IE 6 and Opera 9.24). So stop thinking about it.

I also tried something like this:

Function.prototype = dojo;
$ = new Function("return dojo.query.apply(dojo, arguments);");


but that fails for what I believe to be similar reasons as my first prototype attempt.

I am very interested if someone can suggest how I can modify the $ function's prototype lookup, so that it uses the dojo object. It is neat to think about defining an object that also supports the () operator on it (with a custom prototype).

I want something that works with today's browsers, not ECMAScript 4. Although seeing an ECMAScript 4 implementation might be instructional for the future.

Saturday, July 28, 2007

Ajax Experience Talk about Dojo XD Loader

I attended the Ajax Experience West conference this week, and gave a talk about Dojo's XD loader. It was my first time giving a talk in that venue. I got some good feedback from Naveed (from the dev.aol.com group): I was talking a bit too fast, and didn't make enough eye contact, focusing too much on the slides. The content was good, but I could improve on the delivery. I was suspecting as much, but it was great to get the good feedback. Apparently I'll get feedback (hopefully) from the talk evaluation sheets.

It was neat talking with one of the jQuery contributors, Yehuda Katz, who has been thinking about how to handle serving jQuery plugins. They have been considering a way to load plugins, and possibly specify dependencies. They may not need the full xd dependency mapping, but it would be neat to share some ideas in that area. In the talk, I talked about trying to modify other toolkit code from YUI, Ext and jQuery plugins to load via the dojo loader. I still might try that as an experiment.

In the presentation, I talked a bit about xd loading in general, and to address the security concerns of how to verify the code you are xd loading has not been corrupted or changed. I was thinking using digests of some kind would be good. Ideally, the browser could do it before it evaluated the JS code.

I mentioned the idea to Douglas Crockford, and he had nice idea of being able to specify more than one URL for the script, to serve as backups in case there was a failure with one of the first one failed. He thought it would make a good browser plugin. The multiple URL thing also came up as a suggestion in the Q&A part of the talk, but in the context of dojo.registerModulePath().

Initially, part of the Q&A discussion was ways to do the digest checking without needing a browser plugin, but the more I think about it, it really needs to happen by the browser, since we need to do the check before the imported script is evaluated in any way. And if we are doing xd-loading of the script, that means (at least today) that a script tag is going to be used. So we need to extend the functionality of the script tag.

Looking around at the web, it seems like (as usual) these ideas have been in the air before. I went to Andreas Andreou's cross-site js sharing post, which pointed me to this moz.dev.platform group discussion. This mozilla bug was referenced, and from there, I went to Gervase Markham's Link Fingerprints page.

The Link Fingerprints seems like a workable system. Most of the discussion has been focused on sharing of library code, but it also seems to dovetail nicely with a security aspect. I might ask the moz.dev.platform group about the status of the mozilla bug and mention my desire to have it for security concerns. I'll also ask about supporting alternate backup URLs for download. Maybe as nested script tags?

If this works out, I would feel much more comfortable strongly suggesting xd loading for folks. It would result in a safer web. Very nice.

Saturday, July 07, 2007

TinyBuddy IM: Instant Messaging for iPhone

(Update 7/15/2007: I've moved the TinyBuddy IM-related info over to another blog: Tiny Notes)

Have an iPhone, but wish you could IM your buddies? Now you can, with TinyBuddy IM. It is an AIM® Enabled web-based IM tool. It works by using the Web AIM API, OpenAuth and Dojo.

The nice thing about this solution: you do not send your AIM password to me -- you are redirected to AOL's OpenAuth servers for authentication. My JavaScript only sees an auth token. Furthermore, my web page has to get your explicit consent before accessing your buddy list data and before sending the first IM or presence change.

So the price of this added security is pop-up windows. A new window will be popped so you can authenticate with the OpenAuth servers, and also when giving consent to the application to access your buddy list and IM. For the consent prompts, you can choose "Grant Always" to avoid them on subsequent logins. I think the pop-ups are worth the added security, and at least in iPhone's Safari, window popping looks neat. Unfortunately, the OpenAuth Sign In and Consent pages are made for larger windows, so you will have to double-tap zoom to read them.

Another neat feature of this web application: it is pure JavaScript, HTML and CSS. No server-side languages needed. Dojo really made it easy to do this. I used Dojo 0.4.3 because I want to reuse this code for some other projects that are on 0.4.3, but if/when I get enough time, I would like to port it over to the 0.9 code.

So give it a whirl if you like. I'm sure the code it not bulletproof, and I've noticed enough weirdness with iPhone's Safari to guarantee that I will not be able to give comprehensive support. Also, even though I'm an AOL employee, AOL does not endorse this project or have anything to do with it (but thanks to my co-workers for ideas and early testing, all done of their own accord).

Also, I'm using an OpenAuth dev key, so if there is too much usage you might see some rate limit errors, but we'll see how it goes.

Some other interesting tidbits:
  • The Web AIM API is a Comet API. It uses long polling to work cross-domain in the browser. I'm using more of a short poll with pauses between the polls to hopefully smooth out network hiccups on the phone.
  • Don't like the CSS? You can make your own and tell the app to use it instead. Go to the test launcher page to specify the path to your CSS. Click the Launch button, then copy launch URL. Use that URL when you want to use the application. This feature is not allowed for IE browsers given its security problems with CSS "expressions".
  • I'm serving the code gzipped. The HTML, CSS and JavaScript combined come to about 90 KB. So it is tolerable on the EDGE network.
  • Use the iPhone two finger scroll to scroll the buddy list and IM conversations.
  • Typing IMs should be optimally sized for use with the virtual keyboard. Just type in the text box at the bottom of the IM window and press "Go" on the keyboard.
  • I'm using Dojo Accordions for the IMs and buddy list. I like the use of space with that model and that I can show you incoming IM text if that IM AccordionPane is in the closed position.
  • onbeforeunload does not seem to fire for iPhone Safari. That makes it hard to log out correctly, so to clear your OpenAuth cookies, be sure to use the Available, Sign Out menu item.
To use TinyBuddy IM, just type http://tybyim.com in the iPhone Safari browser. You can try it in other browsers, but it looks best in the iPhone Safari.

Friday, May 25, 2007

Searchable Ajax with JavaScript controllers and a headless Gecko

I'm back from a much appreciated vacation, and just wanted to jot down some thoughts that I want to expand on later. This is a continuation of how I want to build web applications on the RIA-end of the web scale, but an approach I could use for just about any data-driven web site.

I only want to use the server to serve files and to implement data storage. There should be no server-side work dealing with the UI controller. This means the "application server" part of the server (the PHP, Java, .Net, RoR part) should only be handling the API to save/modify/get the data. It should not be doing any presentation or UI flow. JavaScript, CSS and HTML should be used for the presentation and UI flow.

So, the app server implements the data API as a REST API. This gives a good boundary of what should be done in the app server. The data should use XML (preferrably ATOM) or JSON as the data markup. The only presentation part might be to apply something like an XSLT transform of the data if the GET request matches a browser or searchbot user agent. Alternatively, use the domain name of the GET request to know whether or not to apply the XSLT transform (sometimes it is nice to host the pure data API on a different domain for security and load reasons).

Progressive Enhancement should be used for adding the JavaScript controller to the page, so that hyperlinks to other resources can be followed by searchbots or non-JS enabled browsers. Editing of the resources via an HTML interface could be restricted to JavaScript-enabled browsers (mostly where a UI controller is needed anyway, and search bots only care about indexing GET resources anyway).

So that is a baseline. This baseline now cleanly separates the UI controller from the server side, and makes it less relevant what sort of app server technology you use. The UI controller is kept in the JavaScript realm, no more splitting it between JavaScript and [Struts, RoR, Django, etc...].

Now it is time to jump the shark:

Restrict the data API to its own domain, separate from the domain used for presenting the UI for the browser/search bots. For example, use api.domain.com for the data API and ui.domain.com for the browser/search bot interface.

The HTML files on ui.domain.com use Ajax-like techniques access the resources on api.domain.com to show the UI. The Ajax-based data loading would have to be a cross-domain ajax. If the API and UI domains share a common sub-domain, document.domain tricks could be used. Otherwise (and more interesting to me personally), JSONP or XMLHttpRequest IFrame Proxy could be used.

A nice benefit of this approach is that all of the UI pieces (HTML, JavaScript and CSS) are highly cacheable and servable from very different domains than the API domain.

So what about search bots? They cannot do the Ajax techniques, at least not yet. For them, in the web server config, route their user agents to a web server module that uses Gecko to fetch the page, and do the ajax loading. The HTML can send an event to Gecko (this custom, headless gecko can export a function, something like window.onAjaxFinished()) to tell it when it is done rendering. Then the Gecko module serializes the current state of the DOM and sends that HTML string back to the search bot. It could even keep a local cache if it liked, if the resources did not change that often.

This approach would allow the search bot to get a good representation of the page for their indexing, and since Progressive Enhancement was used to bind to resource hyperlinks in the document, the search bot can still navigate to other resources on the domain (and those requests would be funneled through the web server's headless Gecko module).

In this model, REST via HTTP becomes the new JDBC. And more importantly to someone like me who enjoys the front-end -- I don't need to learn about any of the app server flavors of the month to implement data access (the other app server-hugging developers on my team can do that).

So now I just need to do a custom build of a headless Gecko that I can use in a web server module. Any pointers are appreciated. Ideally someone should do this as an open source project so everyone can benefit.

Tuesday, October 24, 2006

IE 7 and IFrame APIs Part 2

Microsoft was kind enough to look into the issue, and here is more information.

The "Navigate sub-frames across different domains" preference is designed to address this security report:
http://secunia.com/advisories/11978/

IE 7 has shipped with automatically turning this preference on.

There is a workaround to the issue: the two iframes that do the communication must both have a parent frame from their domain loaded.

So, the original setup I use in the XHR IFrame Proxy situation looks like the following. The dashes indicate the level of nestedness (Top Window contains IFrame 1, IFrame 1 contains IFrame 2):

Top window (Domain A)
--IFrame 1 (Domain A)
----IFrame 2 (Domain B)

Where IFrame 1 and IFrame 2 communicate by setting each other's fragment identifiers (location.hash in JavaScript). When the communication is done, IFrame 1 tells the top window what the result is.

What broke was IFrame 2 (on Domain B) setting IFrame 1's location (via parent.location).

The following iframe structure works in IE7:

Top window (Domain A)
-- IFrame W (Domain B)
---- IFrame 1 (Domain A)
------ IFrame 2 (Domain B)

Notice the introduction of IFrame W. Now, apparently since IFrame W (on Domain B) owns IFrame 1, it is OK for IFrame 2 to change the location of IFrame 1.

Here is a test of this structure (thanks in large part of Microsoft).

This test uses something I personally have not done before: using window.open() to grab named iframes. Interesting technique. I'm curious to see if it is required when I patch IFrame XHR for IE 7.

So, the end result is that *another* IFrame is needed to accomplish the task. I'll be looking at modifying XHR IFrame Proxy code to use this extra frame, but only for IE 7. The down side is that now, in addition to the remote server placing xip_server.html on their server, they will have to place another HTML file too (the file for IFrame W). If the code bloat is not too much, I might consider making xip_server.html smart enough to act as IFrame W content as well as being the content for IFrame 2.

So, while it works, I still ask Microsoft to consider the following:

1) It seems like this change to address the security issue (http://secunia.com/advisories/11978/) seems like it should not affect setting parent/child frames. Firefox seems to have addressed the security issue without breaking subframe communication. I know that is not entirely a fair comparison, but it seems like the code change in IE to protect against a specific issue has too broad of an effect.

2) Even with this preference on, allow changing of fragment identifiers. It is a non-destructive change to the parent frame, and the parent frame can choose to ignore it. It is also a very solid, useful tool for cross-domain communication that works for today's browsers.
If you want to get fancy, consider supporting a security setting on the iframe, that allows Domain A to say it trusts content from Domain B to change its location. Ideally this sort of configuration of an iframe would be some sort of standardized access control scheme that other browser makers could implement if they so desired.

Wednesday, October 11, 2006

IE 7 Breaks IFrame APIs that use parent.location

Update (Nov. 7, 2006): Microsoft provided more information. See Part 2.

Original post (Oct. 11, 2006):

One of my co-workers was recently testing IE 7 RC1 and found that using fragment identifier messaging (FIM) with IFrames did not work. When the content in the nested IFrame tried to set parent.location, IE 7 popped a new window instead. If you have an IFrame-based API that uses parent.location, then you might be affected by this change.

I would like Microsoft to reconsider the preference change in IE 7 that causes this issue, and this post describes why. Feel free to contact Microsoft if you feel likewise. More information below.

When I investigated the issue more, I found that IE 7 only popped a new window if there was a nested IFrame. If there was just an IFrame in the top level document/browser window, it did not pop a window.
We contacted Microsoft through our company's Microsoft channel, and found out the following:
  • There is a preference that was introduced in Windows XP SP2 that controls this behavior. You can find it by looking in the Internet Settings -> Security Tab -> Click on Custom Level button -> "Navigate sub-frames across different domains".
  • With IE 6, this is set to "Enable" by default.
  • When IE 7 installs, IE 7 automatically sets the preference to "Disable" by default.
I would prefer that they do not change the preference for the following reasons:
  1. There is no documentation about the change, and the change was done without checking with the web developer community.
  2. I think the behavior is buggy and could use refinement.
  3. Fragment identifier messaging (FIM) across IFrames is the best thing we have for allowing pure browser, cross-domain web APIs.
If you feel the same, or if you feel that your own IFrame-based web API that sets parent.location might be affected, then send feedback to Microsoft. I will try to find out where we can send feedback and update this post when I have the info.

More information on why I think IE 7 should keep the preference as "Enabled":

No public documentation of discussion

Our Microsoft contact did not provide any public info indicating this was documented or discussed publicly. When I asked for a description of the change, there was no documentation that was ready for public consumption yet. There might have been some public disclosure about it, but I am not aware of any. I apologize if there was something posted. I have been subscribed to the IEBlog for a while now, but none of the previous posts triggered any warning bells for me. But I could very well have missed it.

This page describes that security preference, but reading does not seem to be very helpful (and seems to discuss talking to sub-frames and not parent frames).

I do not mean to chastise Microsoft on this point. I can appreciate with such a large project as a browser, it might not always be evident what changes can have an adverse effect on people using your product. And even if they did note it somewhere, there are lots of changes, and it could very easily get lost. It has happened to me on my software projects.

I can also see that their answer could be "why don't you just test with the IE 7 betas and release candidates? If there is an issue, let us know". That is a fair and valid position to take. But just as it might be hard to document and message all the changes for a large product like IE 7, it can be hard for web developers to stay on top of everything. I can see many not taking IE 7 testing seriously until RC 1.

And given that the parent.location works as long as you are communicating with a browser window and not an IFrame, I can see for some developers using IFrame APIs, there might not appear to be an issue.

For my case, with the XHR IFrame Proxy, I just worked out that solution July 31st. I haven't done IE 7 testing because I have been busy, and I thought the big things for that release were CSS-related changes (from a web developer POV). There did not seem to be that many JavaScript changes (besides an apparent increase in the JS engine performance). But still, I feel bad getting around to this issue this late in the IE 7 cycle.

So, ideally both of us could have caught the issue sooner and talked about it, but it is understandable that it did not happen. And I hope that since it is a preference change, that changing the IE 7 behavior back to "Enable" is hopefully an easy thing to do even this late in the IE 7 cycle. This would give more time to discuss the issue.

Behavior is buggy and could use refinement

I am sure there is a good use case to justify having this preference, and perhaps it even describes the current behavior. However, from my outsider's point of view, it seems buggy.

Why is Test Case 1 allowed (setting parent.location on the top browser window), but setting the parent.location on an IFrame is not allowed? They both seem to be "cross frame" communication, and violate whatever security issue the preference tries to address.

In addition to addressing the apparent bug with the behavior, I think setting parent.location should be allowed if the new value for parent.location is the same protocol, domain, port and path, but just differ by fragment identifier. In those cases, the parent document is not destroyed or modified, so perhaps whatever security issue is being addressed with this preference does not apply? Allowing fragment identifier changes is important for the next point...

FIM is the best thing we have for allowing pure browser, cross-domain web APIs

There are other ways to do cross-domain communication in a browser, but they all suffer from some limitation that does not make them a good general purpose, pure browser solution.

I also believe Fragement Identifier Messaging (FIM) that is used as part of the XHR IFrame Proxy is the most secure cross-domain communication option, since it requires explicit action for a server to allow the cross-domain action, and it has very fine grain control on the types
of requests that are allowed. FIM also does not automatically "execute" the response as using dynamic script tags might. The receiving frame can choose to just ignore messages if it wants.

And with the exception of the current behavior in IE 7 RC 1, FIM works in all other (major) browsers: IE 6, Firefox 1.5 and 2.0, Safari 2.0, and Opera 9.

The real solution would be a native XMLHttpRequest that can do cross-domain requests, but even when that becomes available, it will take a couple of years at the least to be widespread enough that it is actually be usable by web developers for cross-browser solutions.

In Conclusion

Microsoft, please do not change the preference to "Disable" with IE 7. Keep the default "Enable" behavior of IE 6. If at some point you want to change the preference to "Disable" by default, consider a public discourse of the implementation issues discussed here and the impact on FIM.

Wednesday, August 30, 2006

How I want to build web applications

Use a web server and browser technology (HTML, JavaScript, CSS, etc...) to build the application. Do not use an application server, or any kind of server that is more than a simple web server. Ideally, I could serve the application from the local file system and it would work.

Data service APIs should be usable by JavaScript running in the browser. The data services can be implemented in any choice of server, but the API should not indicate the server implementation. For instance, if I'm using a getItems service, it should not have getItems.jsp/getItems.php/getItems.aspx as part of the API URL.

The web application should be runnable from any domain on any web server. This means the data services must support some sort of cross-domain access from a web browser. My preferences:
The web application might need to access data services that require authentication. The authentication service should be able to support the cross-domain techniques mentioned above. IFP XHR is the more robust option, so it might be preferred when using services that require authentication.

I believe this approach gives the best overall value with scalability and perceived performance when compared to server cost. Scalable because the code can live on any web server, and well-performing because the entire application can be heavily cached by the browser. There is a first time load cost to fetch the files, but after that, the files should have very long cache times. This gives great perceived performance to the user -- things start to visibly happen in the browser window as soon as the user goes to the application URL.

There are solutions that might give you better scalability or better performance, but this approach gives the best overall value when considering server cost. Only simple web servers are needed, and with the high cacheability of the application, the web servers will not need to do work for every invocation of the application. The user's computer and browser are doing more work, but since many (most?) of the applications and services are free, or nearly free, I believe this is an acceptable tradeoff. Of course there are exceptions to this rule, in particular cell phone web applications. However, I do desktop/laptop web application development at the moment.

A very important point about this development approach -- *anyone* can run the web application. Every ISP gives you some amount of disk space on their servers to serve files. This development approach allows you to use that ISP space to run a web application. And I believe that is a key driver to allowing widespread "mashup" usage. Anyone with a text editor, access to the internet, access to cross-domain data service APIs, and a web browser can make a web application usable by the whole world. I am not talking about just serving some web pages, I mean serving web applications: email, instant messaging, calendar, a map service mashup. That is beautiful.

Tools I want to use

A web server
Preferably Apache, but any solid web server that allows for setting cache controls on files will do.

A solid JavaScript toolkit
I prefer Dojo, because I contribute to it, and it offers a nice breadth of libraries with an include system, dojo.require(), that allow you to do progressive optimizations on the code you use without having to recode a bunch of your JavaScript. Once the accessibility and internationalization support is fully integrated, it will be a hard toolkit to beat.

A non-server, i18n-friendly templating system
JSP, ASP, PHP and Rails enable some nice templating systems, but they all require server infrastructure. Tagneto is my attempt at a templating system that does not need server infrastructure. It requires a compile-time step by the developer, but the output is just plain HTML/JavaScript/CSS runnable from any web server (even local disk).

Dojo has some support for a type of templating via widgets and the i18n work going in now, but I don't feel it is as performant as something that you can do with a compile step. Most of the templating work (and in particular i18n string work) could be done once in a compile time step instead of downloading all the code to do the template transformation and doing the transformation every time the code is loaded.

The downside with a compile step, is that the developer needs to run a bunch of compiles while developing. One thing I would like to do is enable development using Dojo for the templating needs, but then apply Tagneto during a build process. Or expand Dojo to do the work as part of its build process. Or just use Tagneto, but allow using it from something like Jetty on the local computer, and have that do the transform on the fly. Once the developer is finished, run the compile step as part of the build to generate the final static files that can be served from a web server.

But for me, I'm fine with doing the compile step as part of the development process. At least for now, until I commit more time to solving the problem. I want to help get Dojo 0.4 out the door first.

Tuesday, June 06, 2006

Cross Domain Frame Communication with Fragment Identifiers (for Comet?)

A page is served from a different domain than the URL for an iframe in that page. Normally cross domain, cross frame communication is prohibited for security reasons. However, the two frames can communicate with each other by using fragment identifiers (the hash part of an URL, like http://some.domain.com/path/to/page.html#fragmentIdentifier).

Since fragment identifier changes don't reload the page, state can be maintained in each of the frames.

This could be used to allow cross domain usage of an API that uses Comet as its communication with the server. Or for UI that a third party wants to embed it, but still allow some stateful communication with the hosting page.

The limitations:
  • Communication is limited to the size of fragment identifiers. I'm not sure on the max size for all browsers, but I would think the same limitation on the size of GET URLs probably hold here too. So the max size for the full URL should probably be kept under around 1KB.
  • Using the iframe may cause issues with the back button.
Test page.

Monday, May 22, 2006

dojo.io.ScriptSrcIO and Dojo 0.2.2

dojo.io.ScriptSrcIO allows you to use dojo.io.bind() but use script tags instead, effectively giving you cross-domain data access (but the data returned as to be JavaScript).

It was introduced for Dojo 0.3, but here is a version of that IO transport that works with Dojo 0.2.2:

http://tagneto.org/dojo/0.2.2compat/ScriptSrcIO.js

In order for it to work with 0.2.2. the back button integration was commented out. Also, since this is not a package that is included with the Dojo 0.2.2 release, you can either place it in the src/io directory of your Dojo install, or if you don't have access to the dojo install, just do a script src tag for this file, right after the dojo.js script tag:

<script src="/path/to/your/dojo/dojo.js" type="text/javascript"></script>
<script src="/path/to/your/copy/of/ScriptSrcIO.js" type="text/javascript"></script>

Then you can use this transport as described in the test page:
http://archive.dojotoolkit.org/nightly/tests/io/test_ScriptSrcIO.html

When you upgrade to Dojo 0.3, be sure to remove the extra script tag if you have it (since ScriptSrcIO.js ships with 0.3).

Wednesday, March 29, 2006

JSONRequest, Part 2 (Cross Domain Policy for All)

As mentioned in part 1, instead of introducing a new request object, JSONRequest, I would prefer to fix the current available pathways. However, I can appreciate that it may be hard to do that. If JSONRequest does go forward, here are some suggestions. The goal of these suggestions are to get JSONRequest to behave in a safe, useful way that could be ported to the other pathways, like script src or XMLHTTPRequest, if it all seemed to work out (and maybe one day merge JSONRequest with XMLHTTPRequest into a generic request object).

The main goal of these suggestions are to protect legacy server systems that did not expect cross domain requests. These suggestions require a server to do explicit actions to allow cross domain behavior. This should address most of the security concerns while laying the groundwork for a general cross-domain request model for the browser.

These suggestions are only for cross-domain requests made with JSONRequest. XMLHTTPRequest's same origin behavior can be used for JSONRequests to the same origin server.

I do not have any particular attachment to the names of the attributes and headers used below. They are just used for illustration of the concept.

The suggestions:
  • Allow Cookies with a xdallow Attribute
  • Request Origin Header
  • Cross-Domain Policy for Pages
  • Allow GET requests to Allow Caching
Allow Cookies with a xdallow Attribute

To alleviate the concerns about data stealing and impacts on legacy systems, introduce a new attribute for cookies, xdallow. New server services could use this new attribute to indicate that it wants to allow cookies to be sent in JSONRequests that originate from a page that is outside of the server's domain. Like today's cookies, the cross domain page cannot actually see the value of these cookies, but if it makes a JSONRequest to a server that set these cookies, they would be sent (as long as the request conformed to the policy set by this new cookie attribute):

Using an example from the cookie spec, current cookies can look like this:
Set-Cookie: CUSTOMER=WILE_E_COYOTE; path=/; domain=anvil.acme.com; expires=Wednesday, 09-Nov-99 23:12:40 GMT
The new attribute would look like this:
Set-Cookie: CUSTOMER=WILE_E_COYOTE; xdallow=XDALLOWVALUE; path=/; domain=anvil.acme.com; expires=Wednesday, 09-Nov-99 23:12:40 GMT
where XDALLOWVALUE could be:
  • xdallow=all - all pages are allowed to use a request to the origin server with this cookie.
  • xdallow=domain.one.com,domain.two.com - a list of comma-separated domains
  • xdallow=http://a.domain.com/with/path.html - a full URL to allow. If the server wanted to allow multiple domains, send multiple Set-Cookie headers with different xdallow values.
  • xdallow=/path/to/policy.xml - A path to the server's cross-domain cookie policy. It would be in XML format and could allow specifying a whitelist and black list of domains and paths that are allowed.
Request Origin Header

The spec uses a Domain header to specify the server that is making the request, and the value is derived from document.domain. I would prefer not to rely on document.domain, since it is a script property that can be changed to a limited degree.

I would prefer to use the page's real domain and include the path of the page making the request. Effectively like the Referer header. I would like to just use the Referer header, but I can see for cross-domain requests only, a new header name should be used. In particular, if these changes were ever merged with the XMLHTTPRequest capability, a header that cannot be modified by a setHeader command should be used. So, perhaps use a new header name like:
Request-Origin: http://some.domain.com/path/to/page/making/request.html
This header would even be sent for https requests. If this is problematic, then don't allow https pages to make cross domain requests.

Cross-Domain Policy for Pages

This is a mechanism for checking if a web page wants to allow cross domain requests via JSONRequest. This is to mitigate the case when the web page is compromised by malicious script. If JSONRequest is asked to make a cross-domain request, it first checks with the server that the page originates from for a xdallow policy file. This policy file could be similar to the one mentioned in the cross-domain cookies section.

If the example page is at the following URL:

http://some.domain.com/path/to/web/page.html

JSONRequest would check the following paths in this order:
  1. http://some.domain.com/path/to/web/.xdallow/page.html.xml
  2. http://some.domain.com/path/to/web/.xdallow/
  3. http://some.domain.com/.xdallow/
Allow GET requests to Allow Caching

The spec does not allow for caching of responses or GET requests. If the cookies are not allowed for JSONRequest, I believe the majority use case for JSONRequest will be for non-authenticated APIs, and a majority of those cases could benefit from cached responses. I don't believe POST requests can be effectively cached by browsers or proxy caches, so it is important in that respect to allow GET requests. Respecting cache headers would also be required of JSONRequest.

If all of the above recommendations are used, it helps protect GET requests. Legacy services would still be protected since they don't support JSON responses. As servers roll out support for JSONRequest, they will be aware of the above requirements to allow cross-domain usage.

In general, these additions force newer services to do explicit actions to allow the cross-domain requests and the additions give those services the information to make intelligent, secure decisions. Legacy systems are protected if they don't opt-in to these requirements. They would have very strong protection by just disallowing any request with the Request-Origin header.

Tuesday, March 14, 2006

JSONRequest Thoughts

This is feedback related to JSONRequest.

Specific detail comments

I'm curious why it is designed as a static method invocation and request ID vs. XMLHTTPRequest's instance-based approach. I would prefer to match XHR's instance-based approach, unless there is a good reason to avoid it. It would be good to match the XHR idiom as much as possible, since developers are familiar with it.

I also like the idea of request limiting on failure count. It would probably be good even for scenarios when it is not an attack, but the server is just having problems. No need to pound the server relentlessly.

General Problem Comments

In general, it feels too limiting, and I'm not sure the limits do much in the end to make it a viable alternative to just dynamically adding SCRIPT tags to the HTML head to load cross-domain scripts and/or JS data. With the dynamically added SCRIPTS, you get cookies sent to the domain, and have the option to get more than just a JSON object.

It is dangerous, but the point is it is possible to do it today. The only thing missing is being able to do a POST and getting a standard callback to know the script has loaded. I tried solving those things with the Dynamic Script Request (DSR) API.

Even though it can be dangerous, developers can use it today, and if it solves their problem, then they will use it. If it is possible to get browser makers to do some work in the area of cross domain script usage, I would prefer to see that effort used to strengthen the existing method and to provide the ability to specify a page-level script policy concerning cross domain scripts.

Strengthen Existing Pathway

I would like to see the following things added to to script tags, and/or have a ScriptRequest object to encapsulate the behavior (I don't really care about the names of these attributes, just what capabilities they allow):
  • method="POST"
  • data="url encoded data sent as the POST body"
  • sync="true" (to get blocking requests)
  • onload="event callback that really worked and was called when the script was loaded"
  • createScope="true" (or something that says "don't treat read the data and do the equivalent of a window.eval () on it. Instead, treat the data as specific scope or as instance object).
Maybe instead of onload, onreadystate, to allow Comet-style connections (the "Duplex" section in the JSONRequest document)?

The HTTP request headers could not be modified by the script request object (except indirectly, in the case of the cookie header). In addition, I would require that a Referer HTTP header be sent with the request. There was a comment on the WHATWG mailing list that Referer is not sent for HTTPS requests. I think something needs to be sent to indicate the calling domain and page path in the HTTP headers so that the server handling the request can make a decision on whether to allow the request.

Page Level Cross Domain Script Policy

Cross domain XML policy files on the server (like the ones used by Flash) do not seem sufficient. As far as I know, those only allow setting domain-to-domain trusts, but there could be many types of web applications on a domain (one HTML page can be a whole application). Sites can host pages from many users.

It should be possible to specify a cross domain script policy in a web page. Something that would set the permissions for scripts that come from other domains than the current domain. It could even be used for any exterior scripts that are loaded, even ones on the same domain. I'm not too particular on that, but it might be a good idea.

The policy would be specified by an HTML element, a child of HEAD. I do not care what the tag is called, but for the purposes of illustration, I'll use a META tag. But again, I do not have any special feelings about using META:

<meta name="ExternalScriptPolicy" content="dom=yes,cookie=no,request=no">

Different script capabilities can be allowed/disallowed, like whether to allow:
  • DOM access
  • Cookie access (maybe break it out by read and write)
  • Request access (whether the external script can use XMLHTTPRequest or ScriptRequest)
  • Other restrictions/privileges?
This tag could not be modified by script.

Conclusion

JSONRequest has some nice ideas, but since there is a gaping hole with SCRIPT tags anyway, I would prefer to use the browser makers time to improve the SCRIPT pathway, make it better to identify request origins, and provide page-level script security policies.

Wednesday, January 25, 2006

Clarifying Tagneto's mission

I think the description of Tagneto in the Why and What documents may be a little vague or too generic. I'm trying to clarify the mission and the message of the mission. Here goes:

Provide tools to build fast, rich web applications that don't require application server infrastructure.

Creating rich user interfaces in HTML should not require a server infrastructure. A web server is needed to serve the files and to distribute updates, but server infrastructure like an application server (whether it is in Java, Python, Ruby, Perl, PHP, .NET, whatever) should not be needed. Server infrastructure is needed for data APIs, but for the user interface, it should not be required.

For some larger applications, it is desirable to have the ability to create the user interface from component HTML pieces, and assemble them together in the page. Normally, an application server technology like JSP, ASP, PHP would be used to accomplish this task. However, since the desire is to avoid application server infrastructure, a View Assembly tool is needed that would provide that capability. Tagneto's View Assembly tool does just that, and it uses tools the UI developer already needs to know, JavaScript, XML, HTML and Entities.

The View Assembly tool also implements advanced features such as
  • "Aspected Oriented HTML" via overlays, allowing the matching and processing of tags across pages, and allowing processing before, after, as the first child or as the last child of the matching tag.
  • Chained tag handlers, for combining many small, focused tags together to accomplish something big.
  • The architecture is also designed to allow other scripting languages (Python, Ruby, Perl) to be used instead of JavaScript for the tag scripting (but right now only JavaScript is implemented).
  • The source does not have to completely valid XML.
  • Internationalization/Localization support.
To help with the "fast" part of "fast, rich web applications", the View Assembly tool uses a developer, compile step to generate the final UI instead of trying to do everything dynamically at runtime in JavaScript. This allows the most efficient use of processing power: use the developer's box to build things that don't depend on dynamic user input, and save only the request/user-dependent UI generation for the runtime JavaScript.
Avoiding application server infrastructure opens up the possibility to deliver the user interface from anywhere, a specialized web farm like a Content Delivery Network (CDN), or even the local disk.

However, to successfully access the data APIs, cross-domain access to the APIs via JavaScript is required. Something that is not possible with XMLHTTPRequest. Ideally, future advances of the XMLHTTPRequest object will provide that functionality, but for the browsers of today, The Dynamic Script Request API is an attempt to solve that issue.

All that said, application server infrastructure is a critical component of delivering a complete application. Hopefully it is just used for the data APIs, but the View Assembly tools should play nice with those technologies if they also need to be used to generate UI. Tagneto's View Assembly tool does play nice with the technologies, since it can be run before deploying the pages to the application server, and it is very flexible in choosing which tags to match and process.

The main goal of Tagneto is not necessarily to provide a "JavaScript library framework". Some JavaScript libraries are available with Tagneto, but in the service of the mission above. In fact, recently I have been considering providing View Assembly tool integration with some of the more well-known JavaScript frameworks, like Dojo or Prototype, with Dojo integration probably first.

Perhaps for Dojo, "View Assembly tool integration" means:
  • Using Dojo's string building utilties to build the JS-escaped strings for the ctrl tags. Right now the Array.append/join is used.
  • Supporting Dojo's connect() methods for event binding via the ctrl:listen tag.
  • Possibly do some compile-time "preprocessing" on Dojo widget tags to convert them to the HTML/JavaScript that will actually be used at runtime. This would avoid having to scan the HTML at runtime to find and bind the widgets.
  • Integrating the DSR with dojo.io.bind.
Those are just some brainstorming ideas. I'll have to investigate the Dojo code to determine feasibility. But I think one of the results of clarifying Tagneto's mission is the focus on not trying to build a JavaScript framework library. Some JS libraries will be developed as reference implementation for pieces that fulfill the mission, but ultimately, I may try to integrate those implementations into existing JS frameworks.

I'll see how it goes, but right now I like this clarification. I'll gradually work on updating the site docs to reflect it. Feedback or suggestions are welcome.

Tuesday, January 17, 2006

Using DSR to allow On-Demand JavaScript for RSS/ATOM feeds

It would be great to see data services provide a JavaScript version of their APIs to allow web developers that don't have (or want) server resources to implement data mashups. JavaScript APIs that can be used via HTML script elements allow for cross-domain data access without server intermediaries.

With the release of Tagneto 0.4.0 there is an update to the Dynamic Script Request (DSR) API, an API that makes On-Demand JavaScript via HTML script elements reliable and easy to use.

The API now uses a reserved callback function, window.onscriptload, and an event object passed to that method to communicate the success/error codes and the data associated with the response. Other browser events and the XMLHTTPRequest object were used to guide the names of the event properties.

These changes allow for data services that provide RSS/ATOM feeds (or any type of XML data feed) to provide a JavaScript response that is equivalent to the normal XML response without too much work (basically encode the XML as a JavaScript string, and put it in an event object that is passed to window.onscriptload).

The API document has more detail on how a RSS/ATOM feed might be provided as JavaScript in the Possible Applications section.

Tagneto 0.4.0 provides a Dsr.js helper script that implements the DSR API for use in a web page, and there is a DSR jar file that can be used in Java Servlet applications to send DSR responses.

Tagneto's home page uses the DSR JavaScript library and the DSR jar for the "Recent Blog Entries" section as a demonstration of the API and the implementation.

Monday, January 16, 2006

Tagneto 0.4.0 Released

The focus on 0.4.0 was on improving and simplifying the Dynamic
Script Request (DSR) API
. From the Release Notes:
  • Significant changes to the DSR API. Decided to go with a reserved global callback handler, onscriptload, to simplify things. Other browser events and the XMLHttpRequest object were used to guide the format and names of the methods and parameters. The result is a much simpler API. A sample usage of the API for RSS and ATOM feeds is described.
  • The Tagneto distribution includes a server/dsr directory. This directory contains a DSR jar that helps servers implement the DSR API for use in web applications that use Java Servlets. A sample WAR file that uses the jar is also included. The JAR and WAR file should run with JRE 1.4+.
  • The Dsr.js file has been updated to the new API.
  • The Tagneto distribution includes the JavaScript libraries in a js directory at the top of the distribution.
  • Tagneto's home page has been updated to use the new DSR js and jar libraries for the "Recent Blog Entries" section.

Sunday, January 01, 2006

Tagneto 0.3.2 Released

The main focus of the release was the JavaScript libraries that support doing dynamic script requests.

See http://tagneto.org/ReleaseNotes.html for more information.

Thursday, December 15, 2005

Referrer test page / Authenticated script src APIs

Referrer test page

I have a test page up now to test the HTTP referrer header values:

http://tagneto.org/test/reftest

If you press the test button, it should show the date from the server and the referrer header value.

There is also another URL to try in the text box: /cgi-bin/redirect.rb. That URL will redirect to the /cgi-bin/refer.rb. I wanted to be sure a redirect did not change the referrer value.

Now I'm going to use the test page to test posting from different pages and in different scenarios to see if I can get the referrer to change to some value than the one from the actual page making the SCRIPT SRC request. If you want to try posting to http://tagneto.org/cgi-bin/refer.rb from other domains, save the index.html page from the reftest URL mentioned above, as well as the SvrScript.js file that is in that same directory.

Authenticated script src APIs
If the referrer thing holds up, then I think it will be possible to proceed with some of the ideas mentioned in this post. However, that post assumed the authorization issue concerned a developer user name using the API on his/her own page.

For the larger issue of allowing a third party web site that deals with data that needs user authentication (without exposing the auth credentials to the third party web site), what about this:
  • 3rd party web site gets an API key. That API key is only bound for certain domains and/or URLs.
  • When a user uses the 3rd party web page that uses a protected data API, the data API server looks for authentication credentials in cookies that are only set to the data API server domain.
  • If the user is not authenticated, then respond to the web page with an error, message of "auth.needed". The 3rd party web page puts up a DIV dialog saying that authorization is needed. If the user says OK, the 3rd party calls an "authenticate" JS method that is provided by the data service script library. An argument to authenticate method would be a return URL (an URL on the 3rd party site). The authenticate method would pop a window an prompt the user for auth credentials. A new window should be used so the user can check the domain/URL of the auth credential page. After successful login, the new window sets its location to the URL passed to authenticate. That URL on the 3rd party site should just close the window and force a refresh/update of the web page that uses the data API.
  • If the user is authenticated, then the data service checks its own database to see if the user has explicitly granted data access to the 3rd party web page URL. If the user has not granted access yet (the data API would look at the referrer HTTP header), then data API responds to the web page with an error, "auth.needPermission". The 3rd party web page calls a "askPermission" JS method that is provided by the data service script library. askPermission would take a JS method callback as an argument. This method creates an IFRAME dialog in the page that asks if the user wants to give permission to this website to access the data. There could be a "Remember this answer" checkbox too, if the data API wanted to allow that. If the user says OK, then askPermission notifies the callback of the answer. The callback could then update the page accordingly (probably by re-calling the data API).
More experiments to do, but it seems promising.

Monday, December 12, 2005

Ctrl.js event listening updated

I was reading more on the web about registering event listeners in JavaScript. Mainly:
  • http://www.quirksmode.org/blog/archives/2005/10/_and_the_winner_1.html
    (including comments)
  • http://dean.edwards.name/weblog/2005/10/add-event2/
  • http://www.quirksmode.org/js/introevents.html
  • http://www.dustindiaz.com/rock-solid-addevent/
I reworked the event listening methods in Ctrl.js to do the following:
  • modify the events that are used in MSIE to look more like the standard events.
  • have "this" point to the element that has the event listener.
  • allow the ability for any JavaScript object to have listeners.
  • allow for cleaning up to avoid memory leaks for individual DOM elements (and optionally their children). Ctrl does register a global cleanup function on document unload, but it also surfaces a removeListeners() method for cases when there are multiple nodes attached/detached within the web page's lifetime, and memory leaks want to be contained as nodes are detached before the page is unloaded. (Even does cleanup for Mozilla because of bug: https://bugzilla.mozilla.org/show_bug.cgi?id=241518
I also renamed the methods, and now there is only one "register event listener" method, Ctrl.listen().

The changes are in CVS but not visible on the web site yet. Still need to round out a little more testing.

Friday, December 02, 2005

Security and the Dynamic Script Request API

Possible Security Issue

Dynamically adding SCRIPT SRC tags to pull in data as JavaScript is nice since it gets around cross-domain issues, but it could also be problematic if you are providing user data, data that required the user to authenticate in some way.

If the User A authenticates for using some JavaScript data APIs via SCRIPT SRC tags, it is possible that Hacker B could guide User A to Hacker B's page that includes a data source that uses User A's credentials.

User A authentication could have been through setting cookies that will travel to the data API domain. The API could also require that User A have an "API key" to use the API (User A registers with the data service provider, and receives a API key text string that must be passed to any data calls. Google Maps is an example).

Hacker B could find out User A's data API key, and if the API required authentication cookies to be set, Hacker B just needs to be sure User A authenticates before coming to Hacker B's page.

Protection Measures

Data Service Registration
  • The user must register for an API key. To obtain the API key, the user must authenticate with the data service provider to prove their identity.
  • As part of this registration, the user must specify which domains are allows to use the API key, and possibly which user names are authorized to use the API Key.
  • Since the user is authenticated, that user name can use the API. Any other user names that are added to the API key must receive an email notification. The other user names must authenticate and grant permission to be on the allowed users for the API key.
Script SRC requests
  • In addition to sending the API key, there must be a timestamp as part of the request. These parameters should be querystring parameters (like dataapi.com?key=xyz&stamp=55959833920)
Data Service Server
  • The server for the data service should not allow caching of the data request. They should set the appropriate HTTP headers to expire the results of the request immediately, to avoid Hacker B taking advantage of the browser cache (that is why query string params are used on the URL too).
  • The server will reject any request that does not have a registered domain in the Referrer HTTP header.
  • The data service domain should only have data services. It should not allow any user-generated web pages under that domain. Ideally it should be unrelated to any domains that host user web page content -- no subdomains should match to prevent document.domain tricks from working.
Test scenarios

Even with these measures in place, are there other holes? I want to make some tests to verify the following:
  • Does authenticating with the data service pass the cookies along properly for SCRIPT SRC requests?
  • Does the Referrer field get set correctly for SCRIPT SRC requests? In all browsers?
  • How can the Referrer field be spoofed? Use XMLHTTPRequest. But hopefully since only data services are allowed on the data service domain that can't be used. Hopefully the document.domain protection above will help too. Is there some way a server proxy running on Hacker B's site be used? Hopefully the User's authentication cookies can't travel to that domain.
  • Even if the server sets expiration headers, will the browser still cache? Hopefully since a timestamp is sent and the URL uses query string params, Hacker B's URL won't be the same anyway, and therefore a different browser cache entry?
These are just first thoughts. I haven't checked yet to see if someone else has already worked this out. And it would be good to do some testing too, think of other scenarios that might break.