Dear community,
we relatively often run into the following issue. There is a webpage with "rich content" that makes requests in the background (either AJAX requests, or simple synchronous POSTs). For example SCORM packages who push their data via the JavaScript API to the backend, or some "content block refresher" code that fetches HTML snippets to be injected into the page, et cetera... Often these applications expect the result to be in a certain format (e.g. JSON) or need to process the HTTP return codes etc..
However, if the user "loses" her session (e.g. by logging out in another tab, or by waiting too long so that it expires), weird things happen to these applications because the "endpoints" at the backend (e.g. simple adp/tcl) cannot be reached anymore. Instead of the expected response (e.g. a plain text SCORM error code), the response is (a redirect to) the login page (HTML).
This is simply because the request processor requires the user to have "read" permission to the package (ad_conn object_id), see [1]. The anonymous user gets a redirect to the login page, triggered by [2].
So how can we deal with this?
A first thought would be to implement the site/application in a way that prevents these requests from getting sent in the first place, e.g. by checking the user's login status in a loop at the client side (without prolonging the session). Thus, the client would know about the users session state and act accordingly (e.g. presenting a dialog "click here to re-login", etc...).
Another workaround is to make the package public and shift the permission checking to the application package. This, however, is often not desired, as admins can change permissions via the UI, (there might be default permissions set by mounting-mechanisms (e.g. dotlrn app installation), and because this can lead to insecure application packages if not implemented rigorously (e.g. checks in every adp/tcl pair, or routing everything through one index.vuh)
Another workaround I often see is to route these requests through "special endpoints" mounted under special locations (e.g. a xhr.tcl in the global /www) that do not have the restrictive permissions of the nested site node. I personally don't like this approach as it spreads the code around in the system.
Another workaround – maybe the ugliest – is to inspect the response HTML to see if it looks like the login page.
I would personally prefer to see this issue solved generically in the core. First of all, one can question the request processors default. Is it really necessary to hardcode the "read" permission requirement? What if I want to realize a "write only" scenario (okay, not that typical, but conceivable – e.g. letterbox)? Even if the read requirement is sensible, is it really the best way to issue a HTTP redirect (302) in the not-authorized scenario? Another option could be to return a 401 status code and issue the login form directly in the content of this 401-page (thinking loud, maybe I overlook something).
A simple, though not sufficient, improvement could be to modify [2] so that if an "X-Requested-With:XMLHttpRequest" header is present, return a simple 401 instead of redirecting to the login page. Unfortunately, this header is not standardized but only set by e.g. jQuery.
So what is the best practice way to do tackle this? Are there any implementation patterns to realize this in a hassle-free manner? Any thoughts?
All the best!
[1] https://cvs.openacs.org/browse/OpenACS/openacs-4/packages/acs-tcl/tcl/request-processor-procs.tcl?hb=true#to758
[2] https://openacs.org/api-doc/proc-view?proc=auth::require_login&source_p=1