Ajax Authentication
From MemberWiki
AJAX (Re)authentication Signaling & Handling for Single-domain and Multi-domain (mashup) applications (Draft Proposal)
0. Authors
- Sumeer Bhola (sbhola@us.ibm.com)
- Suresh Chari (schari@us.ibm.com)
- Larry Koved (koved@us.ibm.com)
- Michael Steiner (msteiner@us.ibm.com)
1. Problem Statement
The AJAX approach to retrieving information from servers does not cleanly handle the case where the request does not contain the needed credentials, or has expired credentials. Typical applications use a login form, and the need for authentication is signaled by sending a HTTP Status-code of 302 that redirects to the login form. For an XMLHttpRequest (XHR) request, the application has to infer that the response, containing an HTML form, means the user has to authenticate. For a ScriptSrc I/O request (like JSONP), the response may not be of the expected content-type so the response is silently dropped.
Another problem is that in mashup scenarios, the widget/gadget that realizes the need for (re)authentication may not be the one who should perform the (re)authentication. One reason is that multiple gadgets from the same domain in the mashup should coordinate user authentication, for usability. Also, the UI (and the user experience) may be orchestrated by the mashup and not the gadget - in the extreme case, the gadget may be a hidden iframe isolated from the mashup.
2. Scope of Proposal
The proposal includes two complementary parts:
- Client-Server interaction: Protocols for signaling the need for authentication, for different client-server communication techniques: XHR and its variants (same-domain XHR, cross-domain XHR, Microsoft's cross-domain XDR), ScriptSrc I/O.
- Client-side interaction: A signaling framework in the JavaScript application, that decouples the entity receiving the unauthorized response, and the entity performing the authentication. Additionally, the framework coordinates multiple concurrent unauthorized responses from the same site, such that it only results in one authentication.
The goal is to handle:
- multiple client-server communication techniques: XHR and its variants, ScriptSrc I/O.
- multiple ways of storing these credentials in the client, and of presenting these credentials to the server: cookies, authorization header, custom header, URI query parameters etc.
- multiple authentication approaches: direct or federated authentication.
By standardizing these, we can simplify interaction between software components written by different entities: different sites, such as for mashups, or different programmers. In addition much of the code implementing the protocols can be moved into client or server frameworks/toolkits and not have to be written by application programmers.
Non-goals:
- Explicit support for Basic Auth: Basic auth cannot be supported for ScriptSrc I/O. For XHR and Basic auth the browser should already handle all the signaling. However, this proposal indirectly supports Basic auth in the following manner: the user is presented with a login page where performing some action (like clicking a button) results in the user being presented with a basic auth dialog.
<Observation> Unfortunately, Firefox 3 does not pop up a dialog box for Basic auth with a 40[123] response status-code, when using XHR. Safari has similar behavior. So the above non-goal does not imply that the Basic Auth with XHR problem is solved. However, we expect browser vendors to address this, and so is not in the scope of this proposal. </Observation>
- Explicit credential passing across different application entities in the browser: The entity receiving the authentication request, and the one performing the authentication may be communicating through an untrusted middle-man. So credentials acquired as part of authentication are not communicated over the same channel and instead browser-side storage that is restricted by domain should be used.
3. Terminology and Assumptions
An application running in the user's browser may be monolithic or consist of multiple application entities. We will represent an application as a "container" with zero or more "gadgets". The current draft does not consider gadgets further consisting of sub-gadgets.
The container and gadget may be built using zero or more Ajax "toolkits".
The OpenAjax alliance provides a JavaScript library, OpenAjax.Auth that is included in each browser frame that wishes to use the signaling framework. This library builds on top of OpenAjax.Hub.
4. Client-Server Signaling Protocol
The signaling protocol consists of signaling in the request that this is an Ajax request, and signaling the need for authentication in the response.
4.1 Request Signaling
For XHR, the request signaling is done using a request header
org.openajax.auth.request: true
For ScriptSrc I/O there is an additional URI query parameter org.openajax.auth.request, set to true.
Alternatively, if the server-side application cleanly separates REST services, and does not support ScriptSrc I/O, then it does not need any explicit signal to these REST services: all requests are assumed to be XHR requests (this is harmless for server-server requests).
4.2 Response Signaling
If the request is determined to be an Ajax request that needs authentication, and not ScriptSrc I/O, the failure response contains:
HTTP/1.1 402 Unauthorized
WWW-Authenticate: XHRAuth realm="..." [,...]
<Observation> The reason for using the 402 status-code instead of 401 is that Opera and Safari do not pass the 401 status-code back to the application if they do not understand the authentication mechanism. Alternatively, an undefined code like 418 could be used. </Observation>
For ScriptSrc I/O requests, the response status-code is 200, and the body contains a single line of Javascript:
OpenAjax.Auth.requestUnauthorized({requesturi="...", realm="..." [,...]})
where requesturi is the URI that was requested.
<Comment> The requesturi parameter in OpenAjax.Auth.requestUnauthorized is being used as a correlator, to correlate the response with a preceding request. In the presece of intermediaries that rewrite requests, like reverse-proxies, the requesturi may not match what the client requested. To handle this case, clients can embed a correlator as a query parameter in the requested URI, and search for it in the requesturi received in the response. </Comment>
The OpenAjax.Auth.requestUnauthorized function is not implemented by the OpenAjax.Auth library. It will be implemented by the application or one of the toolkits used by the application.
The realm defines a protection space. It is a global identifier, so it is recommended that the domain that needs authentication be included in the realm string. The authentication challenge may contain other name-value pairs in addition to the realm. XHRAuth can be considered a meta-mechanism, where the particular mechanism being used depends on the list of name-value pairs included in the challenge. We define one particular mechanism, since it is likely to be common.
4.2.1 Authentication using a new window
The challenge contains authWindowURI and visibility name-value pairs. For example,
WWW-Authenticate: XHRAuth realm="example.com", authWindowURI="https://example.com/login", visibility="true"
The authWindowURI defines the URI of the new window that should be used to authenticate the user. The visibility value states whether the new window needs to be visible or not. Typically, visibility will be true, but if the window's user interface is not needed (such as when using a Microsoft Cardspace Identity Selector), it can be set to false.
5. Client-side Signaling Framework
One of the goals of the client-side signaling framework is to decouple the application entity (container or gadget) receiving the unauthorized response, and the application entity performing the authentication. This decoupling requires communication between entities. The communication is performed using OpenAjax Hub (Hub 1.0 or Hub 1.1).
Application entities have two roles wrt OpenAjax.Auth, complaining about an unauthorized response, and registering to handle unauthorized responses.
5.1 Registration to handle unauthorized responses
1) Call OpenAjax.Auth.registerHandler(realm:string, authHandler:function). For a particular OpenAjax.Auth object, at most one authHandler can be registered for a given realm. If more than one registerHandler is called on an OpenAjax.Auth object for the same realm, the last handler registered will replace the preceding ones. Note that this does not restrict multiple authHandlers to be registered for the same realm, at different OpenAjax.Auth objects (since there is one such object corresponding to each browser window object, and an application can span multiple browser windows).
2) The function authHandler.getHubInstance() returns a connection handle to the hub-instance to use for client-side signaling. If it returns null, Hub 1.0 is used (Hub 1.0 limits communication to within the same browser window object). Note that this method limits the authHandler to be used with only one hub-instance. The same authHandler can be registered multiple times, for different realms.
3) The function authHandler.doAuth(challengeInfo:object) will be called if someone needs authentication. The challengeInfo contains all the name-value string pairs in the challenge, including the realm.
4) After authentication success or failure, the authHandler calls OpenAjax.Auth.doneAuth(realm:string, success:boolean).
An OpenAjax.Auth object manages the state such that for a particuar realm and hub-instance: between a doAuth call to a particular authHandler (say x) and the next doneAuth callback, x will not receive any other doAuth calls. That is, it combines multiple authentication requests for the same realm, to the same authHandler. If concurrent challenges for the same realm have different other name-value pairs, one is arbitrarily chosen.
<Comment> By combining authentication requests, we attempt to enhance application usability by not asking the user to concurrently authenticate to the same domain - say by popping up multiple authentication windows. However, it is not possible to eliminate all race conditions. So it is possible that an authHandler.doAuth call will happen despite the user being authenticated.
A simple example of such a race condition is a resource request being sent just prior to the end of successful user authentication. The resource request fails and by the time the failure response is received, OpenAjax.Auth.doneAuth has already been called.
In certain cases such race conditios can lead to more than just usability issues: if the user reauthenticates due to the failure in the above example, and the new credential invalidates the old credential, then the same race condition can repeat. It is recommended that the authHandler check if the user is already authenticated prior to initiating a new authentication. </Comment>
5.1.1 Topics used in the hub-instance
Under the covers, OpenAjax.Auth subscribes to the topic "openajax.auth.handleUnauthorized", and publishes on "openajax.auth.doneAuth".
5.2 Complaining about an unauthorized response
An application entity sees an unauthorized response either by (1) looking for the 402 status-code and WWW-Authenticate: XHRAuth header or (2) implementing the OpenAjax.Auth.requestUnauthorized function (for ScriptSrc I/O). A toolkit used by the application entity may do some of this on its behalf.
If the application entity wants to authenticate, say because it wants to retry the request later, it does the following:
Call OpenAjax.Auth.handleUnauthorized(challengeInfo:object, callback:function) or OpenAjax.Auth.handleUnauthorized(challengeInfo:object, callback:function, connHandle). If no connHandle is provided (former case), OpenAjax Hub 1.0 is used for communication.
The callback(success:boolean, realm:string) function will be called with true or false depending on authentication success or failure. If success, the application can choose to retry the failed request.
5.2.1 Topics used in the hub-instance
Under the covers, OpenAjax.Auth publishes to "openajax.auth.handleUnauthorized" and subscribes to "openajax.auth.doneAuth".
5.3 Authentication using a new browser window: Communication between authHandler's window and the new window
The authHandler, on receiving a doAuth call, may create a new window object (say window-B) for user authentication, where window-B points to the authWindowURI value in the challenge. The window-B needs to know the challenge values, and needs to communicate authentication success or failure back to the window containing the authHandler (say window-A).
This communication could be done in a non-standard manner, especially when window-A and window-B belong to the same origin. We describe a standard protocol, that can be used in both same-origin and cross-origin scenarios.
Communication from window-A to window-B is done using URI query parameters when window-B is created:
- authWindowURI must not contain a fragment identifier.
- The initial URI of window-B is authWindowURI, plus the following URI query parameters:
- oaa_auth_challenge_realm: The value is the URI-encoded realm in the challenge.
- For each name-value pair in the challenge, other than the realm, the name and value is URI-encoded and included as a URI query parameter, with the parameter name prefixed with oaa_auth_challenge_other_.
- oaa_auth_response_uri: This is a URI in the same domain as window-A, that window-B will be redirected to, at the end of authentication. It must not contain a fragment identifier.
- oaa_auth_response_param: This is a value to pass back when communicating from window-B to window-A.
Communication from window-B to window-A is done by redirecting window-B to the URI in oaa_auth_response_uri. This may be achieved by JavaScript running in window-B that changes window.location. The URI of the redirected window includes a fragment containing the following parameters:
- oaa_auth_challenge_realm: as defined above.
- oaa_auth_success: It is a boolean value which is true if authentication was successful, else false.
- oaa_auth_response_param: as defined above.
The oaa_auth_response_uri window will contain JavaScript code that reads its own fragment identifier and communicates it to window-A (since they are from the same domain). <Comment> The communication from window-B to window-A is done using a fragment identifier, since we would like to avoid a client-server roundtrip. In the presence of frame-phishing attacks (see SMash and Frame Hijacking), a malicious window could redirect window-B to the oaa_auth_response_uri. This can only lead to denial of service attacks which are anyway possible with malicious windows. </Comment>
6. Examples
6.1 Gadgets and Container from same domain
Both the gadgets and the container are from the example.com domain and share the same browser frame. The container calls OpenAjax.Auth.registerHandler to register an authentication handler, authHandler, and specifies OpenAjax Hub 1.0 as the hub-instance. Multiple gadgets see concurrent unauthorized response for requests to the example.com domain. They concurrently call OpenAjax.Auth.handleUnauthorized. One of these results in a call to authHandler.doAuth and the rest are silently dropped. The authHandler opens a browser window pointing to authWindowURI, which is also in the example.com domain. The user authenticates using a login form, and on (un)successful login, the code running in the login window finds the container's window and calls doneAuth on the OpenAjax.Auth object in the container's window. This results in callbacks to all the failed requests.
6.2 Gadgets and Container from same domain using OpenId
This is almost the same as section 6.1, except:
- The authWindowURI may or may not be in the example.com domain:
- If the relying party (RP), example.com, knows the user's identity provider (IP), the authWindowURI may be in the IP's domain.
- If the RP does not know the IP beforehand, the authWindowURI is in the RP's domain. The user enters his/her OpenId in the login window and the login window is redirected to the IP.
- After (un)successful login at the IP, the IP redirects the login window back to the RP.
- The RP server verifies the credential and returns HTML with embedded JavaScript. This JavaScript behaves as in section 6.1: it finds the container's window and calls doneAuth on the OpenAjax.Auth object in the container's window.
6.3 Gadgets and Container from different domains
There is a gadget from foo.com and the container is from example.com. The authentication handler, authHandler, for the foo.com realm is registered by the container since it orchestrates the application UI. The handler is registered with an OpenAjax Hub 1.1 hub-instance. The gadget sees an unauthorized response and calls OpenAjax.Auth.handleUnauthorized with a connHandle to the same hub-instance. This results in a call to authHandler.doAuth.
The authHandler opens a new browser window (called the authentication window) pointing to authWindowURI, which is in the foo.com domain. The communication between the example.com window and the authentication window is done as described in section 5.3. Specifically, the oaa_auth_response_param is the string "window.opener", and oaa_auth_response_uri is "http://example.com/authResponseListener.html". After (un)successful authentication, the authentication window redirects itself to "http://example.com/authResponseListener.html#oaa_auth_challenge_realm=foo.com&oaa_auth_success=true&oaa_auth_response_param=window.opener". The JavaScript code in http://example.com/authResponseListener.html parses its window.location to extract the parameters. The "window.opener" value tells it that it can find the authHandler's window by going to window.opener. It gets the window.opener.OpenAjax.Auth object and calls its doneAuth method: doneAuth("foo.com", true).
7. Misc Issues
- There is no guarantee that a OpenAjax.Auth.handleUnauthorized will always result in a callback, since the framework uses asynchronous events such that the entity calling handleUnauthorized has no knowledge whether there is someone listening to the request. Instead of trying to make it more robust, or add explicit timeout support in the API, this draft proposal leaves timeouts or retries to the application.
