Using ASP.NET MVC 5, I would like to return appropriate HTTP status code for different scenarios (401 for user is not authenticated, 403 when user has no right for some resource, etc.), than handle them in jQuery.
But the problem is, when I try to return 401, it always returns "302: Found". What is the trick for a custom status code, and why this doesn't work?
public ActionResult My()
{
if (User.Identity.IsAuthenticated == false)
{
return new HttpStatusCodeResult(401, "User is not authenticated.");
// Returns "302: Found"
}
// ... other code ...
}
EDIT 1: Interesting bit:
If I replace the 401 with a 404 like this:
return new HttpNotFoundResult("User is not authenticated.");
Then it indeed gives a 404 and jQuery can catch the problem. However it's not an elegant solution as the error code is different.
EDIT 2: 302 is not good for me, as the result would be used in jQuery.get().fail(), but 302 won't triger fail()
Lol this is an awesome problem
The way auth works in MVC is that when you aren't logged in and try to access a secure page it throws a 401 exception. MVC then catches this exception and redirects the user to the login page (which is the 302 you are seeing)
I suppose there's a few things you can do to fix it:
Ignore it, its probably the behaviour you want anyway
Disable login page redirection (phil haack has a good article on this here: http://haacked.com/archive/2011/10/04/prevent-forms-authentication-login-page-redirect-when-you-donrsquot-want.aspx)
EDIT
As per your comments, the following code will turn all redirects into 401s when requested via ajax. This is one approach for avoiding the issue listed
public class MvcApplication : HttpApplication {
protected void Application_EndRequest() {
var context = new HttpContextWrapper(Context);
// If we're an ajax request, and doing a 302, then we actually need to do a 401
if (Context.Response.StatusCode == 302 && context.Request.IsAjaxRequest()) {
Context.Response.Clear();
Context.Response.StatusCode = 401;
}
}
}
Related
I have an app that uses ASP.NET Core MVC as the client and ASP.NET Core WEB API on the back-end. I use a helper method to help me send all the requests to the API and get the response. I am unable to figure out how to handle 403 responses from the API at one place(using something like middleware or filter).
I don't want to do an error code check in every action of a controller. I want to know if we can intercept the response between lines 1 and 2 in the code below.
var response = httpClient.SendRequest(request);
if(response.StatusCode == StatusCodes.Status403Forbidden) {
// redirect to some page.
}
I have tried to add a middleware(like shown below) to do the same and included at the beginning of the startup.cs class but the response in the context is always 500 and not the correct error code(403 in this case).
public async Task Invoke(HttpContext context)
{
if (context.Response.StatusCode == StatusCodes.Status403Forbidden)
{
context.Response.Redirect("Page to redirect");
}
await _next.Invoke(context);
}
Whenever there is an error code(Ex: 403 forbidden) sent from the API, I would like to redirect the users to a specific page from a single place and don't want to check for the status code in every action.
You can use the UseStatusCodePagesWithReExecute provided middleware.
You can register it with like that (depending on the action you actually want to perform) :
app.UseStatusCodePagesWithReExecute("/Home/Error", "?code={0}");
The interpolated variable {0} contains the status code, and it can be passed to the controller called during reexecution, in this case the HomeController, Method Error.
Hope this helps.
I'm developing a webhook receiver for an external tool.
I’m using ASP.NET WebHooks framework (more : https://learn.microsoft.com/en-us/aspnet/webhooks/ )
Everything is working great, except one thing : I can’t return a HttpStatusCode.Unauthorized.
This is a structure of code in WebHookReceiver :
public override async Task<HttpResponseMessage> ReceiveAsync(string id, HttpRequestContext context, HttpRequestMessage request)
{
try
{
var authheader = request.Headers.Authorization;
if (authheader == null || string.IsNullOrEmpty(authheader.Parameter))
{
return new HttpResponseMessage(HttpStatusCode.Unauthorized);
}
I was expecting a simple HTTP response with the code HttpStatusCode.Unauthorized (Side note, ANY other Http code works as expected). Instead it returns the whole HTML Microsoft login page. I don’t even know where it’s taking it from.
My solution for now is just returning “BadRequest” and message “Authorization failed”
Ok got it.
This is perfectly normal.
https://www.restapitutorial.com/httpstatuscodes.html
The request requires user authentication. The response MUST include a
WWW-Authenticate header field (section 14.47) containing a challenge
applicable to the requested resource.
For my case, I should use 403 Forbidden
So I have this code inside the controller of my MVC for a page
[HttpGet, Route]
[Authorize(nameof(Access))]
public async Task<ActionResult> ListStuff()
{
var canRead = HasAccess()
if(!canRead)
{
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
}
I'm using C# attributes for security validation and what I want is if the attribute with the 'HasAccess()' function returns false then the page show show an 'unauthorized' error, as you guys can see I tried throwing an HttpResponseException, I'm pretty sure this isn't the proper way to do it. Any suggestions?
You send a GET http request to your running service, eg:http://localhost:8080/Myservice.svc/$metadata. I've used postman in the past to help with sending http requests.
Link to free postman
My call to RedirectToAction is acting like RedirectToActionPermanent. That is, the URL is being changed, rather than simply displaying a different view.
Edit: Now that I think about it, RedirectToAction typically acts as a permanent redirect. As in, this is probably the correct behavior. In the below code, if the ModelState is valid, the user is given a 302 redirect back to the index. But then, what's the point of RedirectToActionPermanent?
The redirects are for HTTP errors. I have my Web.config set to point errors to certain action methods in HttpErrorsController. This works perfectly, including showing a temporary redirect, as expected. (https://localhost/ThisPageDoesntExist shows error page but the URL remains the same)
Returning an HttpStatusCodeResult or throwing an HttpException both work as expected.
However, if I try to do a temporary redirect to an error action method by using RedirectToAction, the view is still displayed properly, but the URL changes, e.g. https://localhost/HttpErrors/404.
HttpErrorsController.cs
private ViewResult ErrorView(HttpStatusCode httpStatusCode, string shortDesc, string longDesc)
{
Response.StatusCode = (int)httpStatusCode;
return View("HttpError", new HttpErrorViewModel(httpStatusCode, shortDesc, longDesc));
}
[ActionName("404")]
public ActionResult Error404()
{
return ErrorView(HttpStatusCode.NotFound, "Not Found",
"The requested resource could not be found.");
}
// Other identical methods for each error
ItemController.cs
public ActionResult HttpError(HttpStatusCode status)
{
return RedirectToAction(((int)status).ToString(), "HttpErrors");
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(ItemViewModel viewModel)
{
if (!Request.IsAjaxRequest())
{
return HttpError(HttpStatusCode.NotAcceptable);
}
if (ModelState.IsValid)
{
db.Items.Add(pm);
db.SaveChanges();
return RedirectToAction("Index");
}
return PartialView("_Create", viewModel);
}
Since writing the above, I've realized I'm probably better off just throwing an HttpException, so that it also gets caught by ELMAH, but I'm still quite confused by the behavior described above.
RedirectToAction method sends a 302 response back to the browser with the location header value which the new url and the browser will make a totally new http GET request to this new url. So what you see is the expected behavior.
If you want not do the redirect, but want to keep the url as it is, do not do return a RedirectResult, return a view result as needed.
RedirectToActionPermanent method sends a 301 Moved Permanently response back to the client. This is usually useful when you are moving one page to another (killing an old page and creating a new one with different url) of your site and wants the client to know it so that they can the calling code to use the new url in future. Think about google search engine changing the links to your new page and showing that in the search result.
This is sort of an odd question but then I have a very odd situation. On my development server (IIS & IIS Express) I make a ajax request and return a form. The user then posts the form back via an ajax post and if there are any validation errors the controller sends back the partial view with a response code of 400. In my ajax method I intercept any 400 errors and redisplay the form with the validation errors.
My problem is that when I upload my app to my production server all I get on a validation error is the 400 response with no partial view. I don't know even where to begin? Here is what I have tried, what libraries I am using, and some sample code.
ASP.net MVC4,
Fluent Validation,
jQuery, unobtrusive validation, malsup-ajaxsubmit
On my production server...
My partial view that loads the form works as expected. This tells me that the application is having no problem finding my view as the same view is redisplayed if validation fails.
My controller using fluent validation is correctly detecting a model state error and responding with a 400 (just no view with it).
My ajax post using ajaxsubmit is posting to the server correctly using ajax.
Using firebug I step through my ajax method and an 400 error is indeed returned but the only content on xhr.responseText is "Bad Request". NO partial view. Again my development machine works perfectly.
Here is some code.
My Form Get Request:
public ActionResult CreateMedicalEvent(int userId)
{
var model = new EventModel { MedicalEventUserId = userId };
return PartialView("_Event.Create", model);
}
My Form Post:
[HttpPost]
public ActionResult CreateMedicalEvent(EventModel model)
{
if (!ModelState.IsValid)
{
Response.StatusCode = 400;
return PartialView("_Event.Create", model);//this does not get returned on production server
}
//the rest of my code if validation passess
}
My ajax method:
$('.se-form').ajaxForm({
delegation: true,
beforeSubmit: function() {
ajaxHelpers.modalProcess();//modal progress bar
},
success: function(data) {
$.modal.close();
//handle a successful post
},
error: function(xhr) {
$.modal.close();
if (xhr.status == 400) {
slideEdit.seObj.empty();//this is my form object
slideEdit.seObj.append(xhr.responseText);//all I get is "Bad Request" from production server
$.validator.unobtrusive.parse($('form', slideEdit.seObj));
ajaxHelpers.bindDates(); //because we need to bind event AND format date, we have to do this here
utilities.formatDecimal();
} else {
ajaxHelpers.exc(xhr);
}
}
});
I wish there was more code to post but this isn't all that complicated. Am I missing some weird dll file on my production server? Both servers have MVC4 installed. Lost. Pleas help. Thanks.
Adding to #STW 's answer, try adding to your web.config
<system.webServer>
<httpErrors errorMode=”Detailed” />
</system.webserver>
to see the IIS error response.
Also see these links for more
Understanding 400 Bad Request Exception
and ASPNET MVC IIS7 and Bad Request
A 400 indicates a bad request, meaning MVC didn't think it could handle the request being sent--the controller action won't even be invoked in this case. Try enabling detailed error output, or running the request against the site in Visual Studio and see if you can get the exception details. It could be happening in Routing, Controller construction, Action Invoking, or maybe Model Binding.
In my case, I accidentally had the PartialView.cshtml file in the wrong View directory. Moved it over to the right View directory and no more 400 bad request. Doh!