Catches failed AJAX requests for resources under UTLogin where the failure is due to an expired session, allowing the user to login again to retry the action that triggered the original AJAX request. Also protects POSTs by making an AJAX call to ensure the session is still active, and submitting the form on success.
This repo is designed to be used as an upgrade to FIS's existing re-login functionality. As such, it is configured with settings that make implementing it in our applications as easy as possible.
We do want to share this code with other areas that may need re-login functionality in their applications. However, we cannot promise to support any feature requests that do not originate from our own applications. On the other hand, we do have tagged versions of the code that will not change, and anyone can fork this public repo.
jquery.utRelogin.js
:- jQuery 1.7+ (developed against 1.11.1 (included in repo))
- IE 9+, Firefox, Chrome - NOT COMPLETELY COMPATIBLE WITH SAFARI
- Form protection does not currently work in Safari, nor will AJAX calls made with jQuery, or other frameworks that override onreadystatechange after calling xhr.send()
ut_relogin
, the Django app:- Python 2.6+
- Django 1.4+
- Optional: A
UTDirectContext
class
- v1.3.2
- Adding a
__version__
attribute to the module - Decoding
response.content
usingsettings.DEFAULT_CHARSET
before replacing the<head>
tag in middleware. Should allow ut_relogin to work with pages that aren't valid ASCII.
- Adding a
- v1.3.1
- Fixing a bug in v1.3
- v1.3:
- Only attempting to modify property descriptors of xhr.onreadystatechange if supported by the browser. Safari makes xhr.onreadystatechange non-configurable before we can do anything about it, so it is unsupported.
- Adding a cache-busting query string to the script tags in the middleware
- v1.2:
- Made the submit button selector include input elements, too
- v1.1:
- Forms submitted by buttons with name/value attributes should now be hanlded correctly.
- Returning parent page dialog object if it already exists. Fixes the case where the relogin popup is opened twice and autoCloseDialog is true; previously you'd get a JS error when running the callbacks, since the dialog it was trying to close was undefined.
- v1.0: first stable release
To include this app in your PyPE project, simply pull it into your project via
svn:externals
and place it on your PYTHONPATH
. For example, if you're using
the utdirect.utils.setup_extra()
functionality with a project-level folder
named 'extra', you can use these svn:externals
parameters:
path: extra/ut_relogin
URL: https://github.com/UT-Austin-FIS/ut_relogin/tags/v1.3.2/ut_relogin
Then, install it into your Django project:
-
Add "
ut_relogin
" to yourINSTALLED_APPS
setting:# settings.py # ... INSTALLED_APPS = ( # ... 'ut_relogin', # ... )
-
Add the middleware to your
MIDDLEWARE_CLASSES
setting, or add your own subclassed version:# settings.py # ... MIDDLEWARE_CLASSES = ( # ... 'ut_relogin.middleware.UtReloginHeadTagMiddleware', # ... )
-
Set up a URL to be opened in the popup window after login:
-
If you want to use the template and URLconf provided in this app, you must provide a context class and install our URLconf.
1. Tell `ut_relogin` what context class that inherits from
`UTDirectContext` to use:
```python
# settings.py
# ...
UT_RELOGIN_CONTEXT = 'mygroup.myproject.myapp.mycontext.MyContextClass'
```
1. Add our URLconf to your root URLconf:
```python
# urls.py
# ...
import ut_relogin.urls
urlpatterns = (
# ...
url(r'^ut_relogin/', include(ut_relogin.urls)),
)
```
- If you want to write your own page for the popup window, which you need to
do if you're not using
UTDirectContext
, you should add a URL with the nameut_relogin_redirect
that exhibits the behavior you want for the popup window. You also need a URL namedut_relogin_form_protection
that simply returns the text 'ok' to all GET queries.
The resources in this repo can be used in any context. Essentially, this
project is an "app-ification" of the jquery.utRelogin.js
plugin that was done
to make it easier to reliably inject it into our existing Django apps.
If you look at the middleware in ut_relogin/middleware.py
, you'll see the
<script>
tags necessary to add this plugin to your own web application in
whatever language you're using (webAgent, PHP, Java, etc.).
Ideally, you'd include this repo with svn:externals
if you're using Subversion,
or perhaps with subtree merges if you're using git. If you're adding ut_relogin
to a webAgent application, you'll just want to download a tagged version of the
relevant JavaScript files and add them to your source directory.
In addition to getting the two source files and the calling script into your
application, you will probably also want to create a web page to have UTLogin
redirect your users to. The URL to this webpage will need to be provided as a
configuration option to ut_relogin
.
Here's what it should look like:
<head>
<!-- ... -->
<script src="url/to/your/copy/of/jquery-1.11.1.min.js"></script>
<script src="url/to/your/copy/of/jquery.utRelogin.js"></script>
<script>
var $jqUtRelogin = jQuery.noConflict(true);
$jqUtRelogin.utRelogin({
'popupUrl': 'url/to/your/redirect/page.html',
'formProtectUrl': 'url/to/any/page.html'
});
</script>
<!-- ... -->
</head>
The configuration options you can pass to $.utRelogin are the following:
popupUrl
- the URL to open in the login window, to which UTLogin will redirect the user after login
- default:
'/'
popupOptions
- options to pass to
window.open()
for controlling the login browser window - default:
'toolbar=yes,scrollbars=yes,resizable=yes,dependent=yes,height=500,width=800'
- options to pass to
showDialog
- whether to show a dialog on the parent page when opening the login window
- default:
true
autoCloseDialog
- whether to automatically close the dialog on the parent page after logging in, if
showDialog
istrue
- default:
false
- whether to automatically close the dialog on the parent page after logging in, if
formProtectSelector
- the jQuery selector to which to attach "submit" listeners - the listener does a AJAX request to force the AJAX logic to take place before the form submission, preventing submissions while the session is expired
- make it a blank string to disable this behavior
- if binding the submit listener to your forms at document-ready time is not
sufficient, there is a window-global function you can call in your own
JavaScript when appropriate:
window.utrelogin.rebindFormProtectHandlers()
. - default:
form[method=post]
formProtectUrl
- the URL to call to protect form submission
- can be anything; our Django app just returns a plain-text response of
'ok'
- default:
'/'
formProtectRetry
- whether to attempt to automatically resubmit a protected form after login
- default:
false
If you want to do any special handling after login, you can add callback
functions via window.utrelogin.addPostLoginCallback()
. The template included
in the Django app here will call window.opener.utrelogin.postLogin()
, which
iterates through the array of all callbacks that have been added since the last
AJAX call. After calling them all, it clears out the array. This could be used,
for example, to automatically retry an AJAX call after the user has logged in.
To see it in action, look in the utRelogin plugin itself - it uses this functionality to automatically update or close the dialog in the parent window after login.
Before the transition to UTLogin, under
Central Web Authentication
(CWA), AJAX requests that were sent with an invalid session received 200-status
responses from the CWA machinery. Despite being a "success" in HTTP terms,
these responses could be inspected for clues indicating that they in fact
came from failed authentication, instead of containing the intended data.
FIS has a jQuery plugin called utRelogin
that did just that.
Under UTLogin, however, requests with expired sessions are redirected to a central server on a different subdomain, which the browser cannot follow because of its same-origin policy. The browser can't send the original request to this host, so it reports the attempt as a failure. Because the browser handles the original redirect response, there is no way to inspect it from JavaScript code, say to inspect the Location header to see if it's pointing at the UTLogin host.
This project attempts to provide consistent user experience in apps using the
old utRelogin
by detecting expired sessions and giving the user a login
window.
In the old CWA system, every host implemented the same login page. Thus, whenever the user's session became invalid, either because it expired or they logged out, any further requests would be redirected to a path on the same host where the user could login again and get new cookies. The login form would redirect them back to their original location via a GET; any POST data would be lost, and the destination page would be responsible for dealing with the unexpected GET from the logon form.
The older utRelogin
plugin would intercept form submissions and AJAX requests
when the response from the host was the login page. It would put up a modal div
with the login page embedded in an iframe, let the user enter their
credentials, and then close the modal and re-submit the original request. This
no longer works under UTLogin for reasons discussed in the next section.
There are two relevant, major differences in behavior under UTLogin:
- All login pages are served from a single host; while every host under UTLogin has something installed to connect it to UTLogin, the only place that users will be getting login pages is from the central UTLogin host.
- The login page doesn't allow itself to be embedded in an iframe, due to its
X-Frame-Options: Deny
HTTP header. This is a security feature.
Any request made on a host under UTLogin passes through the UTLogin agent software. If a valid session is detected, the request is passed along like normal. If the session is not valid, the agent software will redirect the user to the central UTLogin server. This happens both when the user makes their first request to a new host after logging in, and when their session has become invalid.
When the user requests a resource without a valid session, the agent software on that host intervenes and sends back a redirect to the central UTLogin server. The user then enters their credentials and submits the form. If the credentials are valid, UTLogin issues a POST back to the original resource. The agent on the original host grabs the credentials from the POST body, sets a cookie, and redirects the user to their original destination, using the original HTTP verb and POST or GET data.
For multi-part POST bodies that contain files, not all of the POST data will be
forwarded to the application after redirect. ut_relogin
can help with this
situation by intercepting form submissions and firing an AJAX call before allow
the submission to continue. If the user's session has expired, the re-login
flow described below will take place.
If an application attempts to make an AJAX request after the user's session expires, the UTLogin machinery will redirect the request to the login host; this is a cross-domain request that the browser will prohibit.
Similarly, although most users will have already done a normal request to the host to get the JavaScript code that does AJAX requests, it's also possible that the first request to a host after login in is an AJAX request. For example, the user could have two windows open with pages from two different UTLogin servers. Say their session expires and they re-authenticate on host A; if they switch to their host B window and take an action on the still-loaded page that triggers an AJAX request, that request will fail, since the redirect cycle still needs to happen on host B.
ut_relogin
addresses this situation by giving the user a new login window
that redirects to host B after login. If the user already has a valid session
and only needs to go through the UTLogin redirect cycle, the window that opens
will accomplish that. Otherwise, they will be prompted to enter their
credentials on the normal UTLogin page. Then, the user can redo their action
that triggered the AJAX call, which will now behave normally.