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.