In my ASP.NET MVC4 application, I'm using forms authentication. One view in the app needs to get some string data via a jQuery post() call. I wrote an action that returns string. Everything works fine until the forms authentication ticket expires. After expiration, the AJAX call to the string action results in following:
The code within the action does not get executed
The action returns a HTTP 200 and not a 401
The action returns HTML of the Login view as string
Because 200 status was returned and not 401, the control ends up in jqXHR.done() instead of jqXHR.fail()
Is this expected behavior? Can I do something to make the action return a 401? Or is there something else I should be doing to handle this?
Putting the code in Application_EndRequest() did not work for me. Here is what works in my case:
protected void Application_BeginRequest()
{
HttpRequestBase request = new HttpRequestWrapper(Context.Request);
if (request.IsAjaxRequest())
{
Context.Response.SuppressFormsAuthenticationRedirect = true;
}
}
Yes this is the expected behaviour.
In Asp.Net 4.5 the HttpResponse.SuppressFormsAuthenticationRedirect Property has been added. But the default behaviour is still a redirect to the login page. From MSDN:
By default, forms authentication converts HTTP 401 status codes to 302 in order to redirect to the login page. This isn't appropriate for certain classes of errors, such as when authentication succeeds but authorization fails, or when the current request is an AJAX or web service request. This property provides a way to suppress the redirect behavior and send the original status code to the client.
You could use this property or try the following workaround from this answer:
protected void Application_EndRequest()
{
if (Context.Response.StatusCode == 302 && Context.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
{
Context.Response.Clear();
Context.Response.StatusCode = 401;
}
}
Or you could take a look at this question and answers: Forms authentication: disable redirect to the login page and pick a method that suits you to return a 401.
Related
In my ASP.NET Core application I have this route to sign the user out:
[HttpPost("logout")]
public async Task Logout()
{
await _signInManager.SignOutAsync();
}
and I have this route to check the current username:
[HttpGet("getusername")]
public string GetUserName()
{
return _userManager.GetUserName(User);
}
Yet, when the route is called, the second route continues to behave as if the user is logged in. This question claims that this can happen if the HTTP response that SignOutAsync produces doesn't get processed by the client, but it is requested via AJAX with this code:
async function logout(){
let response = await fetch('/api/user/logout', {method: 'post'});
if(response.ok){
Cookies.remove('username');
window.location.reload();
}else{
alert(`Failed to log out due to networking error: ${response.status} ${response.statusText}`)
}
}
so the change is processed. Why does this code not behave as expected, where the sign out AJAX call ends the session? I'm using Firefox 81.0.1 on Linux as my client.
I figured out the issue. It depends on two unexpected facts about Asp.net Core and Firefox respectively:
When a blank string is returned from an Asp.net Core route, it returns with HTTP 204 No Content.
When Firefox receives a response of HTTP 204 No Content, it does not load the new page, but instead just shows the old page again (with a flash that suggests that an actual refresh is taking place!).
Taken together, when I signed out, the username sign-out occurred as it should on the backend, but I didn't realize it because when I refresh the username checking route in another tab, the username re-appeared. I consider this to be an issue with Firefox.
The response cache works well in Postman But cache does not work in other browsers and "Cache-Control" in browser is " public, max-age=60" .
And every time refresh browser the action method is called .
my Api Code :
[HttpGet]
[ResponseCache(Duration =60)]
public IActionResult GetAllCustomer()
{
Request.HttpContext.Response.Headers.Add("X-Total-Custumer", _h_Plus_SportsContext.Customer.Count().ToString());
return new ObjectResult(_customerService.GetAllCustomer())
{
StatusCode = (int)HttpStatusCode.OK
};
}
You should provide more context, but Chrome and possible other browser are sending by default Cache-control header with a value of 0 (this happens even if Chrome Dev Tools has Disable cache unchecked):
A workaround is to follow a link to your link to be tested or use Back button as indicated here.
I have a web application using MVC and AngularJS, which connects to a Web API 2 api, that I have set up in a separate project.
Currently I am able to retrieve information from the Api with no problems.
However when I try to do a HTTP Post I am getting no response, originally I was getting a problem with the pre-flight request failing, I have now handled this in my controller, however it does not send the proper request after it has got an OK message back.
I have included my code for the Angular Factory and the C# Controller in the API.
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class RegisterController : ApiController
{
public string Post()
{
return "success";
}
public HttpResponseMessage Options()
{
return new HttpResponseMessage { StatusCode = HttpStatusCode.OK };
}
}
var RegistrationFactory = function($http, $q, ApiAddress) {
return function(model) {
// $http.post(ApiAddress.getApiAddress() + '/Register/Post', model.ToString());
$http({
method: "POST",
url: ApiAddress.getApiAddress() + '/Register/Post',
data: model,
headers: { 'Content-Type': 'application/json; charset=utf-8' }
}).success(function(data) {
$location.path("/");
});
}
};
RegistrationFactory.$inject = ['$http', '$q', 'ApiAddress'];
Edit:
I am still not having any joy with this, however I tested in Internet Explorer and it works with no problems at all.
I have got it working in chrome by starting with web security disabled, however obviously this is not ideal as it will not work on a user PC with security enabled.
I see that you have done adaptation for CORS on the server side. But I cannot see any client side (javascript) adaptation. May be you should add the code below before calling the service.
$http.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
Let me know if this fixes the issue. Worked for me in all scenarios :)
It's strange that your GETs work, but your POSTs don't.
I would recommend running the code in Google Chrome with web security enabled (so we can watch it go wrong) and with the F12 Developer Options shown.
Select the Network tab, run your code, and watch what happens when the POST is called.
Does your service return a "200 OK" status, or some other value ?
Does any kind of Response get returned ?
It might be worth trying this, and appending a screenshot of the results in your original question. It might help to identify the cause.
I am still not having any joy with this, however I tested in Internet
Explorer and it works with no problems at all.
Btw, you don't have any single sign-on stuff setup in your company, do you ? We've had issues where IE works fine, but other browsers don't allow single sign-on. Just a thought...
CORS requires a OPTIONS-preflight which has HTTP headers in its response that tell the browser whether it is allowed to access the resource.
E.g. HTTP Response Headers:
Access-Control-Allow-Headers: authorization
Access-Control-Allow-Origin: *
Because you have a custom Options handler in your C# controller, it seems those HTTP headers are not returned, stopping the browser to make the call after the preflight.
Avoid the Options method, and you should be good.
I am trying to return a ActionResult from an MVC api, and if you give it a redirect uri in the post body it should redirect that uri. Right now if you do not give it a uri it performs fine. But if you give it a uri where it should redirect to that uri it just returns a 302 and does not navigate anywhere.
Now I think one issue I am having is that the returned location in the response header is the base URI for the MVC and then the input URI. One problem I have is that all documentation on this I have found, well sucks.
So my question is how do I use the RedirectResult action to actually redirect to a different URI? Also can I redirect to a URI that is outside the current domain? I am worried I cannot since it is appending the base URI to the location in the response. It does not force the clients browser to navigate to another URI.
Here is my action result.
[AllowAnonymous]
public ActionResult Dispatch(Dto.GoogleAnalyticsEvent evt)
{
log.DebugFormat("Dispatch()");
if (evt.Redirect != null)
{
return new RedirectResult(evt.Redirect, false);
}
else
{
return Dispatch<Dto.GoogleAnalyticsEvent>(evt, _api.DispatchAnalyticsEvent, true);
}
}
Here is the returned response if you give it a uri in the post body. The response code is actually a 302 found.
Access-Control-Allow-Head... Content-Type
Access-Control-Allow-Meth... GET,POST
Cache-Control private
Content-Length 139
Content-Type text/html; charset=utf-8
Date Wed, 22 May 2013 22:42:25 GMT
Location /events/www.google.com
Server Microsoft-IIS/7.5
access-control-allow-orig... *
Thanks in advance for the help.
Just to clarify, I was looking for the response from the API to force the browser to navigate to a URI upon response. Though now that I type that out loud it seems that browsers probably would not allow that. How is a 302 found supposed to affect the browser?
If you are calling this action via AJAX (or some other form of direct call to server) the browser will have no way to know that server returned 302 (or 40x, 50x, 200 or any other kind of response).
The behavior is exactly the same as if you get 200+data response for AJAX request - browser will not magically navigate to that response page.
Solution: if you need to redirect as result of AJAX request you need to redesign your protocol to return some 200 response that says "please redirect to this Url" and set window.location.href accordingly on client side when you get such response.
Note that AJAX call will follow 302 redirect and result of the call will be page you are redirecting to (if on the same domain) or "access denied" of some sort if it is cross domain and CORS not turned on destination. See How to prevent ajax requests to follow redirects using jQuery for more background on it.
I'm setting the forms authentication cookie like this below
FormsAuthentication.SetAuthCookie("test", true);
and when i check if its set it returns null...
Context.User.Identity.Name
any ideas why this is happening? thanks
You should always redirect after setting a forms authentication cookie:
public ActionResult SomeAction()
{
FormsAuthentication.SetAuthCookie("test", true);
return RedirectToAction("FooBar");
}
It's only in the subsequent action you are redirecting to that you will get the User.Identity.Name being properly initialized. The reason for that is pretty simple: the User.Identity.Name property is initialized from the Request cookies (a.k.a incoming cookies) whereas the FormsAuthentication.SetAuthCookie is setting the forms authentication to the response (a.k.a. emitting a cookie) so that on subsequent requests this cookie will be sent in the request.