How recognize javascript fetch() inside HandleErrorAttribute OnException? - javascript

In an asp.net mvc application, I'm successfully handling exceptions that occur when I make a jquery $.ajax call from javascript to a Controller. I want to handle errors in the same way when I make a javascript fetch() call from javascript, but I cannot figure out how to recognize that a fetch() call was made.
I'm using a class derived from HandleErrorAttribute to handle all Controller errors. This is done by adding the following line to global.asax Application_Start:
GlobalFilters.Filters.Add(new Areas.Insurance.Helpers.MyHandleErrorAttribute());
In MyHandleErrorAttribute I'm overriding the OnException method so that I can treat Ajax errors differently then regular get or post errors. If there is an ajax error my desire is to return an object that contains a guid and trigger the $.ajax "error:" callback in javascript. If there is an error caused by a javascript fetch() call I want the response.ok to return "false" and pass back an object that contains the error guid. If the error is not an ajax error or javascript fetch() error I want to redirect to our custom error page. The code looks like this:
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.Exception == null)
return;
//log the error
Guid guid = MyClass.LogError(filterContext.Exception.GetBaseException());
if (filterContext.HttpContext.Request.IsAjaxRequest()
&& filterContext.Exception != null)
{
//this is an error from a $ajax call.
//500 is needed so that $ajax "error:" function is hit, instead of "success:"
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
filterContext.Result = new JsonResult()
{
Data = new MyErrorObject()
{
Guid = guid
}
,JsonRequestBehavior = JsonRequestBehavior.AllowGet
//JsonRequestBehavior.AllowGet prevents following sporadic error: System.InvalidOperationException: This request has been blocked because sensitive information could be disclosed to third party web sites when this is used in a GET request. To allow GET requests, set JsonRequestBehavior to AllowGet.
};
//this stops Application_OnError from firing
filterContext.ExceptionHandled = true;
//this stops the web site from using the default IIS 500 error.
//in addition must set existingResponse="Auto" ("PassThrough also works) in web.config httpErrors element.
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
}
else
{
//if condition to avoid error "Server cannot set status after HTTP headers have been sent"
if (!filterContext.HttpContext.Response.IsRequestBeingRedirected)
{
filterContext.Result = new RedirectToRouteResult(
new System.Web.Routing.RouteValueDictionary
{
{"action", "Error500" },
{"controller", "Error" },
{ "Guid", guid}
});
filterContext.HttpContext.Response.StatusCode = (int)HttpStatusCode.OK;
filterContext.ExceptionHandled = true;
}
}
}
My problem is, when a call is made using javascript fetch(), "IsAjaxRequest" returns false, so the first "if" block is not hit. Instead I am going into the "else" block, which sets the Http Response to "OK" and does a redirect to my error page, both of which are not desired (when I get a fetch() error I want to return a 500 response, and of course the redirect has no effect).
How can I recognize that a javascript fetch() call was made? Is there something similar to "IsAjaxRequest"?
Here is what the javascript fetch looks like:
let url = '/myDomain/ThrowException';
fetch(url)
.then(function (data) {
if(data.ok){
//do something with data
}
else{
handleHttpErrorFunction(data);
}
})
.catch(function (error) {
handleFetchErrorFunction(error);
});
And here is the (successfully handled) jquery ajax call that I want to replace with a fetch() call:
$.ajax({
url: '/myDomain/ThrowException',
type: 'GET',
success: function (data) {
//do something with data
},
error: function (XMLHttpRequest, ajaxOptions, ex) {
ajaxerrorhandler(XMLHttpRequest);
}

See answer to related question at From server, how recognize javascript fetch() call was made (asp.net mvc). The fix is to send X-Requested-With XMLHttpRequest in the headers:
fetch('/MyDomain/MyControllerAction', { headers: { 'X-Requested-With': 'XMLHttpRequest' } })

Related

What HTTP response codes does the jQuery aJax error handler consider an 'error'?

I have a standard aJax call back to the server:
$.ajax({
type: 'POST',
dataType: 'json',
contentType: 'application/json',
url: '/Apps/ResetDateAndCount',
data: JSON.stringify({
appName: app
}),
success: function (response) {
$('.lastReset').text(response);
$('.currentCount').text('0');
},
error: function (xhr, status, error) {
alert("You are not authorized to perform that action");
}
});
From my server (ASP.NET) I am returning errors like:
return Json(new { success = false, error = "You are not authorized to perform that action" });
and
Response.StatusCode = 401; return Json(new { success = false, error = "You are not authorized to perform that action" });
and
Response.StatusCode = 500; return Json(new { success = false, error = "You are not authorized to perform that action" });
Inside of the error handler error: function (xhr, status, error) only the last return will be caught as an error, when the status code is set to 500.
I am wondering what response codes aJax actually consider an "error"?
Any error code that is outside of the interval [200, 299] and different than 304 is considered an error by jQuery when making an AJAX call.
Now of course you're gonna ask me, that's fine and dandy but why the hell this is not considered as an error, after all I am returning 401 which outside of the aforementioned interval so it should be considered an error:
Response.StatusCode = 401;
return Json(new { success = false, error = "You are not authorized to perform that action" });
Very simple: because when you return a 401 status code from a controller action in ASP.NET MVC, the framework (and more specifically the ASP.NET Forms Authentication module) will intercept this request and redirect you to the login form eventually resulting in a 200 status code. Yeah, the login page is served with a 200 OK status code. In this case 200 is inside the interval so no error callback is invoked. You can validate this by inspecting the DOM of the login page returned in your success callback for this particular case.
Now put yourself into the perspective of a browser making an AJAX request: It will follow all the redirects that a server makes until it reaches the final destination. If this final destination status code is outside of the interval and different than 304 then you will get the error callback invoked. OK, now things start to make a little more sense.
So the next question you're gonna ask me is how do I invoke the error callback if I return a 401 status code from my controller action, right? Then I will redirect you (:-)) to the following blog post: Prevent Forms Authentication Login Page Redirect When You Don't Want It.
I would suggest checking the list of standard HTTP codes. Please see it here https://en.wikipedia.org/wiki/List_of_HTTP_status_codes. It's self-explanatory.
In other words, any codes >= 400 (4xx, 5xx) can be considered as an error.
You are returning the error message:
"You are not authorized to perform that action"
I'd say, this text message corresponds mostly to 401/403 codes. For 5xx errors, I would suggest you display some server error message.

Modifying the argument passed to jQuery done() callback following a successful AJAX call

Here's the scenario:
I need to fetch some JSON data via AJAX from the server (ASP.NET MVC application + ServiceStack services). If the user's session expires then instead of getting back JSON I get a redirect to the login page, which gets automatically executed by jQuery, so the result of the AJAX call ends up being the raw HTML of the login form. I need to handle these two cases differently:
if the result is JSON then use it to update the UI using the .done() promise
if the result is HTML then pop up a login dialog.
So I have to modify the argument passed to the done() callback. Here's what I've tried so far:
function getSomeData()
{
return $.ajax(
{
/*
...set my AJAX config options...
*/,
error: function(xhr, status, errorText)
{
// handle the error
},
success: function(data)
{
// if the AJAX call returned a login form instead of expected JSON
// result (because the user's session has expired) then show a
// login dialog and resolve the jQuery promise with 'null' instead
// of original 'data'
if (isLoginFormHTML(data))
{
showModalLoginDialog(data);
data = null; // neither this...
return null; // ... nor this seem to work!
}
}
});
}
function updateUI()
{
getSomeData().done(function(jsonData)
{
if (jsonData)
{
// do something
}
});
}
Unfortunately, regardless of whether I do data = null or return null in my success function, the argument passed to the done() callback contains the original data returned from the server, which may be the HTML of the login form if the session has expired.
So the question is, how do I tell jQuery to pass something else to done()?
P.S. Of course, I could perform the isLoginFormHTML() check inside the updateUI method but I'm trying to avoid that in order to keep the data-retrieval and UI-update logic separate.
You should return a promise on the values you need by using .then, one of the cool things about promises is that they chain.
function getSomeData()
{
return $.ajax({
/*
...set my AJAX config options...
*/,
error: function(xhr, status, errorText)
{
// handle the error
}).then(function(data){
if (isLoginFormHTML(data)) {
showModalLoginDialog(data);
throw new Error("Unauthorized");..
}
return data;
}
});
}
Which would allow you to do:
getSomeData().done(function(jsonData){
//yay data
}).fail(function(){
//no data
});

Ajax call (using jquery) gets no more responses after 5 minutes

In fact, I have a JavaScript application (jQuery) that is sending a request to a Java function which is supposed to send back data (success case) to my ajax function.
In the success case, I go on with my application; in error case, I stop my procedures and display the error message to the user.
But when the Java code takes more than 5 minutes to send a response to my Ajax call, this one (ajax code) doesn't (work / respond / go on /) get any response anymore ...
I tried setting up a timeout limit to 600000 ms (so 10 minutes) but I still get the same problem (and ain't got any error messages or output, which could have been helping me).
So if anyone's got an idea on the source of this problem, I'd be grateful.
Here is some piece of code if it may make it clearer :
JSP
$(document).ready(function() {
/* performing some layout changes */
//
preValidationDisplay();
}
function preValidationDisplay() {
/* performing some layout changes */
setTimeout("getValidationResult()", 500);
}
function checkReadLogFile() {
$.ajax({
url: logFileUrl,
cache: false,
success: function(result)
{
$("#progressBarToResize").width(
'' + parseFloat(result.trim()) + '%' );
}
});
}
function getValidationResult()
{
var timer = setInterval("checkReadLogFile()", 1000);
$.ajax({
url: '<s:url value="doValidation"/>',
type: "GET",
cache: false,
dataType: "json",
async: true,
//timeout : 600000,
success: function(result, status)
{
// Do something
},
error: function(jqXHR, status, errorStr){
// Do something
}
});
}
java
#RequestMapping(value = "/doValidation", method = RequestMethod.GET)
public String processValidationResults(HttpServletRequest request, HttpServletResponse response,
#RequestHeader("Connection") String conn) {
ValidationResult validationResult = null;
JSONObject resultJson = new JSONObject();
HttpSession session = request.getSession();
JSONObject progressMap = (JSONObject) session.getAttribute("progressMap");
String uploadedFilePath = progressMap.getString("filePath");
String realMimeType = progressMap.getString("realMimeType");
long fileSize = progressMap.getLong("fileSize");
String ctxPath = request.getContextPath();
// INVOKE the VALIDATION ENGINE
try {
validationResult = validationEngineService.getValidationResult(uploadedFilePath, ctxPath);
resultJson = JSONObject.fromObject(validationResult);
} catch (Exception e) {
validationResult = null;
resultJson.put("errorMsg", e.toString());
}
try {
response.getWriter().print(resultJson);
response.flushBuffer();
} catch (Exception e) {
}
return null;
}
When you're working with HTTP, never expect a connection to take longer than half a minute.
Either use WebSockets or Comet (long polling).
See, there are many parties that might decide to stop your connection:
web browser, firewall/router/isp, web server (eg: Apache), server scripting language (eg: PHP)
The only time I disable this on the PHP side is when I want PHP to keep running even after the user stopped the connection (which, by the way, I have enable yet another setting).
Without some code, i'm not sure if i could help much but have you tried checking which event is triggering the ajax call? I made a silly mistake once by sending an ajax request on keydown, i later found out that instead of keydown , i needed to use keyup since thats the event triggered once the value of the textbox is set to the text + key recently entered.
So you could try a keyup or change the event in jQuery. If this is on localhost, its not a good sign....first determine whats causing the problem, the frontend or backend.
Things to do:
1) Change event type in jQuery
$("#elem").live("keyup",function() {//send req});
2) Check apache log. If your on ubuntu or something similar, you can go to /var/log/apache2/error.log

Accessing MVC Action from another application via javascript

I have an MVC 3 web app that has an Action which I need to access from a webform app via javascript.
My action returns a Json result, which I have checked works just fine. However, when accessing it with javascript from another application, I can see it reaching my action, doing all the work, returning my data, but the jquery function then gives me an error 200 and no data.
Example:
My MVC Action:
public ActionResult GetData(int categoryId)
{
var listofSeries = new List<string>
{
"foo1",
"foo2",
"foo3"
};
return Json(listofSeries, JsonRequestBehavior.AllowGet);
}
This returns me: ["foo1","foo2","foo3"]
Normally, my javascript function is used to query a database and get data back for a Highcharts chart but I have the same issue with this kind of example.
<script type="text/javascript">
$(document).ready(function () {
var foo = getData(9);
});
function getData(categoryId) {
var results;
$.ajax({
type: 'POST',
url: 'http://localhost:xxxx/Home/GetData',
//contentType: "application/json; charset=utf-8;",
async: false,
data: { "categoryId": categoryId },
dataType: "json",
success: function (data) {
console.log("results: " + data);
results = data;
}
});
return results;
}
</script>
With this I get:
http://localhost:63418/Home/GetData?categoryId=9 200 OK
results: null
If I add the contentType, I can't even see the function running in the firebug console, all I see is results: null. When I remove it I do but I get the error above.
I've been told it might not be doable because of "Cross-site scripting". And to instead have my javascript function call a webservice (or handler but I haven't quite figured out how to make one of those yet) that will then call my action, return my data to the service who then returns the data to the javascript.
But now I'm getting a 415 Unsupported Media Type error message when trying to connect to my webservice.
My IWebService.cs:
[ServiceContract]
public interface IWebService
{
[OperationContract]
[WebInvoke(ResponseFormat = WebMessageFormat.Json, Method = "POST")]
void GetData(int mileId);
}
My WebService.svc:
public void GetData(int categoryId)
{
string url = "http://localhost:63418/Home/GetWoWData?categoryId=" + categoryId;
WebRequest wr = WebRequest.Create(url);
WebResponse response = wr.GetResponse();
}
I'm not returning anything yet, it won't even go in this function. Giving me the 415 error message.
If I change the contentType to "text/xml" I get an error 400.
I would really like to have this work without using the webservice, and figure why I get an error 200 OK when I can see the action running, but if that's not possible, any idea why the webservice isn't working?
Thanks.
string url = "http://localhost:63418/Home/GetWoWData?categoryId=" + categoryId;
WebRequest wr = WebRequest.Create(url);
wr.Credentials = CredentialCache.DefaultNetworkCredentials; // uses current windows user
var response = (HttpWebResponse)wr.GetResponse();
I only just remembered I had this post and found the solution a while ago. It was solved by adding the credentials (in my case the current windows user) and getting the response as an HttpWebResponse.
200 makes me think everything is working ok, but IE is using a cached result, i've run into this problem before with IE. try adding:
$.ajaxSetup({
// Disable caching of AJAX responses */
cache: false
});
this will make it append a random parameter, and will stop IE from being stupid.

How to manage a redirect request after a jQuery Ajax call

I'm using $.post() to call a servlet using Ajax and then using the resulting HTML fragment to replace a div element in the user's current page. However, if the session times out, the server sends a redirect directive to send the user to the login page. In this case, jQuery is replacing the div element with the contents of the login page, forcing the user's eyes to witness a rare scene indeed.
How can I manage a redirect directive from an Ajax call with jQuery 1.2.6?
I read this question and implemented the approach that has been stated regarding setting the response HTTP status code to 278 in order to avoid the browser transparently handling the redirects. Even though this worked, I was a little dissatisfied as it is a bit of a hack.
After more digging around, I ditched this approach and used JSON. In this case, all responses to AJAX requests have the status code 200 and the body of the response contains a JSON object that is constructed on the server. The JavaScript on the client can then use the JSON object to decide what it needs to do.
I had a similar problem to yours. I perform an AJAX request that has 2 possible responses: one that redirects the browser to a new page and one that replaces an existing HTML form on the current page with a new one. The jQuery code to do this looks something like:
$.ajax({
type: "POST",
url: reqUrl,
data: reqBody,
dataType: "json",
success: function(data, textStatus) {
if (data.redirect) {
// data.redirect contains the string URL to redirect to
window.location.href = data.redirect;
} else {
// data.form contains the HTML for the replacement form
$("#myform").replaceWith(data.form);
}
}
});
The JSON object "data" is constructed on the server to have 2 members: data.redirect and data.form. I found this approach to be much better.
I solved this issue by:
Adding a custom header to the response:
public ActionResult Index(){
if (!HttpContext.User.Identity.IsAuthenticated)
{
HttpContext.Response.AddHeader("REQUIRES_AUTH","1");
}
return View();
}
Binding a JavaScript function to the ajaxSuccess event and checking to see if the header exists:
$(document).ajaxSuccess(function(event, request, settings) {
if (request.getResponseHeader('REQUIRES_AUTH') === '1') {
window.location = '/';
}
});
No browsers handle 301 and 302 responses correctly. And in fact the standard even says they should handle them "transparently" which is a MASSIVE headache for Ajax Library vendors. In Ra-Ajax we were forced into using HTTP response status code 278 (just some "unused" success code) to handle transparently redirects from the server...
This really annoys me, and if someone here have some "pull" in W3C I would appreciate that you could let W3C know that we really need to handle 301 and 302 codes ourselves...! ;)
The solution that was eventually implemented was to use a wrapper for the callback function of the Ajax call and in this wrapper check for the existence of a specific element on the returned HTML chunk. If the element was found then the wrapper executed a redirection. If not, the wrapper forwarded the call to the actual callback function.
For example, our wrapper function was something like:
function cbWrapper(data, funct){
if($("#myForm", data).length > 0)
top.location.href="login.htm";//redirection
else
funct(data);
}
Then, when making the Ajax call we used something like:
$.post("myAjaxHandler",
{
param1: foo,
param2: bar
},
function(data){
cbWrapper(data, myActualCB);
},
"html"
);
This worked for us because all Ajax calls always returned HTML inside a DIV element that we use to replace a piece of the page. Also, we only needed to redirect to the login page.
I like Timmerz's method with a slight twist of lemon. If you ever get returned contentType of text/html when you're expecting JSON, you are most likely being redirected. In my case, I just simply reload the page, and it gets redirected to the login page. Oh, and check that the jqXHR status is 200, which seems silly, because you are in the error function, right? Otherwise, legitimate error cases will force an iterative reload (oops)
$.ajax(
error: function (jqXHR, timeout, message) {
var contentType = jqXHR.getResponseHeader("Content-Type");
if (jqXHR.status === 200 && contentType.toLowerCase().indexOf("text/html") >= 0) {
// assume that our login has expired - reload our current page
window.location.reload();
}
});
Use the low-level $.ajax() call:
$.ajax({
url: "/yourservlet",
data: { },
complete: function(xmlHttp) {
// xmlHttp is a XMLHttpRquest object
alert(xmlHttp.status);
}
});
Try this for a redirect:
if (xmlHttp.code != 200) {
top.location.href = '/some/other/page';
}
I just wanted to share my approach as this might it might help someone:
I basically included a JavaScript module which handles the authentication stuff like displaying the username and also this case handling the redirect to the login page.
My scenario: We basically have an ISA server in between which listens to all requests and responds with a 302 and a location header to our login page.
In my JavaScript module my initial approach was something like
$(document).ajaxComplete(function(e, xhr, settings){
if(xhr.status === 302){
//check for location header and redirect...
}
});
The problem (as many here already mentioned) is that the browser handles the redirect by itself wherefore my ajaxComplete callback got never called, but instead I got the response of the already redirected Login page which obviously was a status 200. The problem: how do you detect whether the successful 200 response is your actual login page or just some other arbitrary page??
The solution
Since I was not able to capture 302 redirect responses, I added a LoginPage header on my login page which contained the url of the login page itself. In the module I now listen for the header and do a redirect:
if(xhr.status === 200){
var loginPageRedirectHeader = xhr.getResponseHeader("LoginPage");
if(loginPageRedirectHeader && loginPageRedirectHeader !== ""){
window.location.replace(loginPageRedirectHeader);
}
}
...and that works like charm :). You might wonder why I include the url in the LoginPage header...well basically because I found no way of determining the url of GET resulting from the automatic location redirect from the xhr object...
I know this topic is old, but I'll give yet another approach I've found and previously described here. Basically I'm using ASP.MVC with WIF (but this is not really important for the context of this topic - answer is adequate no matter which frameworks are used. The clue stays unchanged - dealing with issues related to authentication failures while performing ajax requests).
The approach shown below can be applied to all ajax requests out of the box (if they do not redefine beforeSend event obviously).
$.ajaxSetup({
beforeSend: checkPulse,
error: function (XMLHttpRequest, textStatus, errorThrown) {
document.open();
document.write(XMLHttpRequest.responseText);
document.close();
}
});
Before any ajax request is performed CheckPulse method is invoked (the controller method which can be anything simplest):
[Authorize]
public virtual void CheckPulse() {}
If user is not authenticated (token has expired) such method cannot be accessed (protected by Authorize attribute). Because the framework handles authentication, while token expires, it puts http status 302 to the response. If you don't want your browser to handle 302 response transparently, catch it in Global.asax and change response status - for example to 200 OK. Additionally, add header, which instructs you to process such response in special way (later at the client side):
protected void Application_EndRequest()
{
if (Context.Response.StatusCode == 302
&& (new HttpContextWrapper(Context)).Request.IsAjaxRequest())
{
Context.Response.StatusCode = 200;
Context.Response.AddHeader("REQUIRES_AUTH", "1");
}
}
Finally at the client side check for such custom header. If present - full redirection to logon page should be done (in my case window.location is replaced by url from request which is handled automatically by my framework).
function checkPulse(XMLHttpRequest) {
var location = window.location.href;
$.ajax({
url: "/Controller/CheckPulse",
type: 'GET',
async: false,
beforeSend: null,
success:
function (result, textStatus, xhr) {
if (xhr.getResponseHeader('REQUIRES_AUTH') === '1') {
XMLHttpRequest.abort(); // terminate further ajax execution
window.location = location;
}
}
});
}
I think a better way to handle this is to leverage the existing HTTP protocol response codes, specifically 401 Unauthorized.
Here is how I solved it:
Server side: If session expires, and request is ajax. send a 401 response code header
Client side: Bind to the ajax events
$('body').bind('ajaxSuccess',function(event,request,settings){
if (401 == request.status){
window.location = '/users/login';
}
}).bind('ajaxError',function(event,request,settings){
if (401 == request.status){
window.location = '/users/login';
}
});
IMO this is more generic and you are not writing some new custom spec/header. You also should not have to modify any of your existing ajax calls.
Edit: Per #Rob's comment below, 401 (the HTTP status code for authentication errors) should be the indicator. See 403 Forbidden vs 401 Unauthorized HTTP responses for more detail. With that being said some web frameworks use 403 for both authentication AND authorization errors - so adapt accordingly. Thanks Rob.
I resolved this issue like this:
Add a middleware to process response, if it is a redirect for an ajax request, change the response to a normal response with the redirect url.
class AjaxRedirect(object):
def process_response(self, request, response):
if request.is_ajax():
if type(response) == HttpResponseRedirect:
r = HttpResponse(json.dumps({'redirect': response['Location']}))
return r
return response
Then in ajaxComplete, if the response contains redirect, it must be a redirect, so change the browser's location.
$('body').ajaxComplete(function (e, xhr, settings) {
if (xhr.status == 200) {
var redirect = null;
try {
redirect = $.parseJSON(xhr.responseText).redirect;
if (redirect) {
window.location.href = redirect.replace(/\?.*$/, "?next=" + window.location.pathname);
}
} catch (e) {
return;
}
}
}
Another solution I found (especially useful if you want to set a global behaviour) is to use the $.ajaxsetup() method together with the statusCode property. Like others pointed out, don't use a redirect statuscode (3xx), instead use a 4xx statuscode and handle the redirect client-side.
$.ajaxSetup({
statusCode : {
400 : function () {
window.location = "/";
}
}
});
Replace 400 with the statuscode you want to handle. Like already mentioned 401 Unauthorized could be a good idea. I use the 400 since it's very unspecific and I can use the 401 for more specific cases (like wrong login credentials). So instead of redirecting directly your backend should return a 4xx error-code when the session timed out and you you handle the redirect client-side. Works perfect for me even with frameworks like backbone.js
I have a simple solution that works for me, no server code change needed...just add a tsp of nutmeg...
$(document).ready(function ()
{
$(document).ajaxSend(
function(event,request,settings)
{
var intercepted_success = settings.success;
settings.success = function( a, b, c )
{
if( request.responseText.indexOf( "<html>" ) > -1 )
window.location = window.location;
else
intercepted_success( a, b, c );
};
});
});
I check the presence of html tag, but you can change the indexOf to search for whatever unique string exists in your login page...
Most of the given solutions use a workaround, using an extra header or an inappropiate HTTP code. Those solutions will most probably work but feel a bit 'hacky'. I've come up with another solution.
We're using WIF which is configured to redirect (passiveRedirectEnabled="true") on a 401 response. The redirect is usefull when handling normal requests but won't work for AJAX requests (since browsers won't execute the 302/redirect).
Using the following code in your global.asax you can disable the redirect for AJAX requests:
void WSFederationAuthenticationModule_AuthorizationFailed(object sender, AuthorizationFailedEventArgs e)
{
string requestedWithHeader = HttpContext.Current.Request.Headers["X-Requested-With"];
if (!string.IsNullOrEmpty(requestedWithHeader) && requestedWithHeader.Equals("XMLHttpRequest", StringComparison.OrdinalIgnoreCase))
{
e.RedirectToIdentityProvider = false;
}
}
This allows you to return 401 responses for AJAX requests, which your javascript can then handle by reloading the page. Reloading the page will throw a 401 which will be handled by WIF (and WIF will redirect the user to the login page).
An example javascript to handle 401 errors:
$(document).ajaxError(function (event, jqxhr, settings, exception) {
if (jqxhr.status == 401) { //Forbidden, go to login
//Use a reload, WIF will redirect to Login
location.reload(true);
}
});
This problem may appear then using ASP.NET MVC RedirectToAction method. To prevent form displaying the response in div you can simply do some kind of ajax response filter for incomming responses with $.ajaxSetup. If the response contains MVC redirection you can evaluate this expression on JS side. Example code for JS below:
$.ajaxSetup({
dataFilter: function (data, type) {
if (data && typeof data == "string") {
if (data.indexOf('window.location') > -1) {
eval(data);
}
}
return data;
}
});
If data is: "window.location = '/Acount/Login'" above filter will catch that and evaluate to make the redirection instead of letting the data to be displayed.
Putting together what Vladimir Prudnikov and Thomas Hansen said:
Change your server-side code to detect if it's an XHR. If it is, set the response code of the redirect to 278.
In django:
if request.is_ajax():
response.status_code = 278
This makes the browser treat the response as a success, and hand it to your Javascript.
In your JS, make sure the form submission is via Ajax, check the response code and redirect if needed:
$('#my-form').submit(function(event){
event.preventDefault();
var options = {
url: $(this).attr('action'),
type: 'POST',
complete: function(response, textStatus) {
if (response.status == 278) {
window.location = response.getResponseHeader('Location')
}
else { ... your code here ... }
},
data: $(this).serialize(),
};
$.ajax(options);
});
<script>
function showValues() {
var str = $("form").serialize();
$.post('loginUser.html',
str,
function(responseText, responseStatus, responseXML){
if(responseStatus=="success"){
window.location= "adminIndex.html";
}
});
}
</script>
Let me just quote again the problem as described by #Steg
I had a similar problem to yours. I perform an ajax request that has 2
possible responses: one that redirects the browser to a new page and
one that replaces an existing HTML form on the current page with a new
one.
IMHO this is a real challenge and will have to be officially extended to the current HTTP standards.
I believe the new Http Standard will be to use a new status-code.
meaning: currently 301/302 tells the browser to go and fetch the content of this request to a new location.
In the extended standard, it will say that if the response status: 308 (just an example), then the browser should redirect the main page to the location provided.
That being said; I'm inclined to already mimic this future behavior, and therefore when a document.redirect is needed, I have the server respond as:
status: 204 No Content
x-status: 308 Document Redirect
x-location: /login.html
When JS gets the "status: 204", it checks for the existence of the x-status: 308 header, and does a document.redirect to the page provided in the location header.
Does this make any sense to you?
Try
$(document).ready(function () {
if ($("#site").length > 0) {
window.location = "<%= Url.Content("~") %>" + "Login/LogOn";
}
});
Put it on the login page. If it was loaded in a div on the main page, it will redirect til the login page. "#site" is a id of a div which is located on all pages except login page.
While the answers seem to work for people if you're using Spring Security I have found extending LoginUrlAuthenticationEntryPoint and adding specific code to handle AJAX more robust. Most of the examples intercept all redirects not just authentication failures. This was undesirable for the project I work on. You may find the need to also extend ExceptionTranslationFilter and override the "sendStartAuthentication" method to remove the caching step if you don't want the failed AJAX request cached.
Example AjaxAwareAuthenticationEntryPoint:
public class AjaxAwareAuthenticationEntryPoint extends
LoginUrlAuthenticationEntryPoint {
public AjaxAwareAuthenticationEntryPoint(String loginUrl) {
super(loginUrl);
}
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
if (isAjax(request)) {
response.sendError(HttpStatus.UNAUTHORIZED.value(), "Please re-authenticate yourself");
} else {
super.commence(request, response, authException);
}
}
public static boolean isAjax(HttpServletRequest request) {
return request != null && "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
}
}
Sources:
1, 2
I solved this by putting the following in my login.php page.
<script type="text/javascript">
if (top.location.href.indexOf('login.php') == -1) {
top.location.href = '/login.php';
}
</script>
Some might find the below useful:
I wanted clients to be redirected to the login page for any rest-action that is sent without an authorization token. Since all of my rest-actions are Ajax based, I needed a good generic way to redirect to the login page instead of handling the Ajax success function.
This is what I've done:
On any Ajax request my server will return a Json 200 response "NEED TO AUTHENTICATE" (if the client needs to authenticate).
Simple example in Java (server side):
#Secured
#Provider
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
private final Logger m_logger = LoggerFactory.getLogger(AuthenticationFilter.class);
public static final String COOKIE_NAME = "token_cookie";
#Override
public void filter(ContainerRequestContext context) throws IOException {
// Check if it has a cookie.
try {
Map<String, Cookie> cookies = context.getCookies();
if (!cookies.containsKey(COOKIE_NAME)) {
m_logger.debug("No cookie set - redirect to login page");
throw new AuthenticationException();
}
}
catch (AuthenticationException e) {
context.abortWith(Response.ok("\"NEED TO AUTHENTICATE\"").type("json/application").build());
}
}
}
In my Javascript I've added the following code:
$.ajaxPrefilter(function(options, originalOptions, jqXHR) {
var originalSuccess = options.success;
options.success = function(data) {
if (data == "NEED TO AUTHENTICATE") {
window.location.replace("/login.html");
}
else {
originalSuccess(data);
}
};
});
And that's about it.
in the servlet you should put
response.setStatus(response.SC_MOVED_PERMANENTLY);
to send the '301' xmlHttp status you need for a redirection...
and in the $.ajax function you should not use the .toString() function..., just
if (xmlHttp.status == 301) {
top.location.href = 'xxxx.jsp';
}
the problem is it is not very flexible, you can't decide where you want to redirect..
redirecting through the servlets should be the best way. but i still can not find the right way to do it.
I just wanted to latch on to any ajax requests for the entire page. #SuperG got me started. Here is what I ended up with:
// redirect ajax requests that are redirected, not found (404), or forbidden (403.)
$('body').bind('ajaxComplete', function(event,request,settings){
switch(request.status) {
case 301: case 404: case 403:
window.location.replace("http://mysite.tld/login");
break;
}
});
I wanted to specifically check for certain http status codes to base my decision on. However, you can just bind to ajaxError to get anything other than success (200 only perhaps?) I could have just written:
$('body').bind('ajaxError', function(event,request,settings){
window.location.replace("http://mysite.tld/login");
}
If you also want to pass the values then you can also set the session variables and access
Eg:
In your jsp you can write
<% HttpSession ses = request.getSession(true);
String temp=request.getAttribute("what_you_defined"); %>
And then you can store this temp value in your javascript variable and play around
I didn't have any success with the header solution - they were never picked up in my ajaxSuccess / ajaxComplete method. I used Steg's answer with the custom response, but I modified the JS side some. I setup a method that I call in each function so I can use standard $.get and $.post methods.
function handleAjaxResponse(data, callback) {
//Try to convert and parse object
try {
if (jQuery.type(data) === "string") {
data = jQuery.parseJSON(data);
}
if (data.error) {
if (data.error == 'login') {
window.location.reload();
return;
}
else if (data.error.length > 0) {
alert(data.error);
return;
}
}
}
catch(ex) { }
if (callback) {
callback(data);
}
}
Example of it in use...
function submitAjaxForm(form, url, action) {
//Lock form
form.find('.ajax-submit').hide();
form.find('.loader').show();
$.post(url, form.serialize(), function (d) {
//Unlock form
form.find('.ajax-submit').show();
form.find('.loader').hide();
handleAjaxResponse(d, function (data) {
// ... more code for if auth passes ...
});
});
return false;
}
Finally, I solve the problem by adding a custom HTTP Header. Just before response for every request in server side, i add the current requested url to response's header.
My application type on server is Asp.Net MVC, and it has a good place to do it. in Global.asax i implemented the Application_EndRequest event so:
public class MvcApplication : System.Web.HttpApplication
{
// ...
// ...
protected void Application_EndRequest(object sender, EventArgs e)
{
var app = (HttpApplication)sender;
app.Context.Response.Headers.Add("CurrentUrl",app.Context. Request.CurrentExecutionFilePath);
}
}
It works perfect for me! Now in every response of the JQuery $.post i have the requested url and also other response headers which comes as result of POST method by status 302, 303 ,... .
and other important thing is that there is no need to modify code on server side nor client side.
and the next is the ability to get access to the other information of post action such errors, messages, and ..., In this way.
I posted this, maybe help someone :)
I was having this problem on a django app I'm tinkering with (disclaimer: I'm tinkering to learn, and am in no way an expert). What I wanted to do was use jQuery ajax to send a DELETE request to a resource, delete it on the server side, then send a redirect back to (basically) the homepage. When I sent HttpResponseRedirect('/the-redirect/') from the python script, jQuery's ajax method was receiving 200 instead of 302. So, what I did was to send a response of 300 with:
response = HttpResponse(status='300')
response['Location'] = '/the-redirect/'
return response
Then I sent/handled the request on the client with jQuery.ajax like so:
<button onclick="*the-jquery*">Delete</button>
where *the-jquery* =
$.ajax({
type: 'DELETE',
url: '/resource-url/',
complete: function(jqxhr){
window.location = jqxhr.getResponseHeader('Location');
}
});
Maybe using 300 isn't "right", but at least it worked just like I wanted it to.
PS :this was a huge pain to edit on the mobile version of SO. Stupid ISP put my service cancellation request through right when I was done with my answer!
You can also hook XMLHttpRequest send prototype. This will work for all sends (jQuery/dojo/etc) with one handler.
I wrote this code to handle a 500 page expired error, but it should work just as well to trap a 200 redirect. Ready the wikipedia entry on XMLHttpRequest onreadystatechange about the meaning of readyState.
// Hook XMLHttpRequest
var oldXMLHttpRequestSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function() {
//console.dir( this );
this.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 500 && this.responseText.indexOf("Expired") != -1) {
try {
document.documentElement.innerHTML = this.responseText;
} catch(error) {
// IE makes document.documentElement read only
document.body.innerHTML = this.responseText;
}
}
};
oldXMLHttpRequestSend.apply(this, arguments);
}
I got a working solulion using the answers from #John and #Arpad link and #RobWinch link
I use Spring Security 3.2.9 and jQuery 1.10.2.
Extend Spring's class to cause 4XX response only from AJAX requests:
public class CustomLoginUrlAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {
public CustomLoginUrlAuthenticationEntryPoint(final String loginFormUrl) {
super(loginFormUrl);
}
// For AJAX requests for user that isn't logged in, need to return 403 status.
// For normal requests, Spring does a (302) redirect to login.jsp which the browser handles normally.
#Override
public void commence(final HttpServletRequest request,
final HttpServletResponse response,
final AuthenticationException authException)
throws IOException, ServletException {
if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied");
} else {
super.commence(request, response, authException);
}
}
}
applicationContext-security.xml
<security:http auto-config="false" use-expressions="true" entry-point-ref="customAuthEntryPoint" >
<security:form-login login-page='/login.jsp' default-target-url='/index.jsp'
authentication-failure-url="/login.jsp?error=true"
/>
<security:access-denied-handler error-page="/errorPage.jsp"/>
<security:logout logout-success-url="/login.jsp?logout" />
...
<bean id="customAuthEntryPoint" class="com.myapp.utils.CustomLoginUrlAuthenticationEntryPoint" scope="singleton">
<constructor-arg value="/login.jsp" />
</bean>
...
<bean id="requestCache" class="org.springframework.security.web.savedrequest.HttpSessionRequestCache">
<property name="requestMatcher">
<bean class="org.springframework.security.web.util.matcher.NegatedRequestMatcher">
<constructor-arg>
<bean class="org.springframework.security.web.util.matcher.MediaTypeRequestMatcher">
<constructor-arg>
<bean class="org.springframework.web.accept.HeaderContentNegotiationStrategy"/>
</constructor-arg>
<constructor-arg value="#{T(org.springframework.http.MediaType).APPLICATION_JSON}"/>
<property name="useEquals" value="true"/>
</bean>
</constructor-arg>
</bean>
</property>
</bean>
In my JSPs, add a global AJAX error handler as shown here
$( document ).ajaxError(function( event, jqxhr, settings, thrownError ) {
if ( jqxhr.status === 403 ) {
window.location = "login.jsp";
} else {
if(thrownError != null) {
alert(thrownError);
} else {
alert("error");
}
}
});
Also, remove existing error handlers from AJAX calls in JSP pages:
var str = $("#viewForm").serialize();
$.ajax({
url: "get_mongoDB_doc_versions.do",
type: "post",
data: str,
cache: false,
async: false,
dataType: "json",
success: function(data) { ... },
// error: function (jqXHR, textStatus, errorStr) {
// if(textStatus != null)
// alert(textStatus);
// else if(errorStr != null)
// alert(errorStr);
// else
// alert("error");
// }
});
I hope it helps others.
Update1
I found that I needed to add the option (always-use-default-target="true") to the form-login config.
This was needed since after an AJAX request gets redirected to the login page (due to expired session), Spring remembers the previous AJAX request and auto redirects to it after login. This causes the returned JSON to be displayed on the browser page. Of course, not what I want.
Update2
Instead of using always-use-default-target="true", use #RobWinch example of blocking AJAX requests from the requstCache. This allows normal links to be redirected to their original target after login, but AJAX go to the home page after login.
As alternative to ajax, there is a new Fetch API being developed, which allows manual redirect handling. You need to check if the current browser support is enough for your needs.

Categories

Resources