Update: The below solution depends on iframes. ADFS 3.0 has X-Frame-Options defaulted to DENY, with no option to change the setting. So this solution will only work on ADFS 2.1 & earlier.
In your global.asax.cs, you're going to want to catch any mid-AJAX 302s and turn them into a 401 Unauthorized. This will prevent the call from proceeding (and popping that message), and will send us to $(document).ajaxError().
protected void Application_EndRequest()
{
var context = new HttpContextWrapper(this.Context);
if (context.Response.StatusCode == 302 && context.Request.IsAjaxRequest())
{
context.Response.Clear();
context.Response.StatusCode = 401;
}
}
Then, in there, intercept any 401s before they proceed to the rest of your error handling. I chose to show a message to the users. You can do the next step right here, but for readability, I'm sending the ajaxSettings object to another function. Return true so it won't proceed into the rest of your error handling.
If you want to doublecheck that this is ADFS, event.target.referrer will have the URL of the attempted redirect.
$(document).ajaxError(function (event, jqXHR, ajaxSettings, thrownError) {
if (xhr.status == 401) {
alert("Your session has timed out. Click OK to reauthorize and extend your session.");
TriggerReauthenticationRefresher(ajaxSettings);
return true;
}
…the rest of the error handling code…
});
I have an empty div in my page just for this situation, with an id of 'refresherBox', but you can do this on any element in your DOM. Put together an iframe that goes to some dummy page in your domain. In my case, the contents of ADFSRefresher.cshtml are just
<div><input type="hidden" value="@DateTime.Now.ToString()" /></div>
Instead of using global variables, I'm storing the ajaxSettings using .data(). We also need to keep track of how many times the iframe reloads, so we're also storing loadcount. Insert the iframe into the DOM, and it will kick off.
function TriggerReauthenticationRefresher(ajaxSettings) {
var refreshframe = '<iframe src="@Url.Action("ADFSRefresher", "Debug")" style="display:none" onload="TrackFrameReloads()" />';
$('#refresherBox').data('loadcount', 0);
$('#refresherBox').data('originalRequestSettings', ajaxSettings);
$('#refresherBox').html(refreshframe);
}
TrackFrameReloads will fire every time the iframe finishes loading. Since we know there is an impending ADFS redirect, it will fire twice. The first time will be the redirect, and the second time will be to its src url. So the first time it fires, we just increment loadcount.
The second time it fires, we know we have been successfully reauthenticated. Retrieve the ajaxSettings, clear the stored data, and you can then re-use your original settings to send the AJAX call! It will go through, un-redirected, and run its original success & complete functions.
function TrackFrameReloads() {
var i = $('#refresherBox').data('loadcount');
if (i == 1) {
alert('Your session has been extended.');
var ajaxSettings = $('#refresherBox').data('originalRequestSettings');
$('#refresherBox').removeData();
$.ajax(ajaxSettings);
} else {
$('#refresherBox').data("loadcount", 1);
}
}
Be aware that if you defined them, the error and complete functions will have already fired.
You can skip the two alert messages to the users if you like. Depending on your ADFS setup, this should only take 1 second, and the user doesn't have to be informed that any of this happened at all!