3

I have the following asp.net web api method to delete a file:

    [Authorize]
    [HttpPost]
    public IHttpActionResult Delete(int id)
    {

        var uploadedFile = unitOfWork.FileRepository.Get(id);

        if (uploadedFile == null)
            return NotFound();

        if (uploadedFile.CreatedBy != User.Identity.GetUserId())
            return Unauthorized();

        unitOfWork.FileRepository.Remove(uploadedFile);

        unitOfWork.Complete();

        return Ok();
    } 

I want to return an unauthorized result if the user attempting to delete the file did not create the file. I have the following ajax to handle the call but upon testing I always get response 200 and so the fail function never get called in my ajax function?

I have debugged the web api method with break points and it clearly fires the Unauthorized method - return Unauthorized();

So why is it returning status 200 when i view in firefox console:

POST http://localhost:65179/api/file/2 200 OK 29ms

When i check the response header in console it shows the following:

 X-Responded-JSON {"status":401,"headers":{"location":"http:\/\/localhost:65179\/Account\/Login?ReturnUrl=%2Fapi%2Ffile%2F94"}}

So im at a loss as to why the fail function is not being called? I thinks it's doing a redirect to the login page hence the status is being returned as 200. How do i suppress the redirect then?

 function () {
        $.ajax({
            url: "api/file/2",
            method: "POST"
        })
        .done(function () {

            alert("File has been deleted.");
        })
        .fail(function ( jqXHR) {
            alert("Unable to delete file");

        });
   });

*** UPDATE ****

I've found the following blog post as a potential solution but it will only work if your project is only web api.

https://brockallen.com/2013/10/27/using-cookie-authentication-middleware-with-web-api-and-401-response-codes/

My project is a combination of MVC 5 and web api 2 so I've amended it and added the code to startup.auth.cs as follows but I've had to comment out the OnValidateIdentity bit inorder to add the OnApplyRedirect to the cookieoptions.

public void ConfigureAuth(IAppBuilder app)
    {
        var cookieOptions = new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            Provider = new CookieAuthenticationProvider
            {
                // Enables the application to validate the security stamp when the user logs in.
                // This is a security feature which is used when you change a password or add an external login to your account.  
                //OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                //    validateInterval: TimeSpan.FromMinutes(30),
                //    regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))

                OnApplyRedirect = ctx =>
                {
                   if (!IsApiRequest(ctx.Request))
                   {
                       ctx.Response.Redirect(ctx.RedirectUri);
                    }
                 }
            }
        };

        app.UseCookieAuthentication(cookieOptions);

    }

    private static bool IsApiRequest(IOwinRequest request)
    {
       string apiPath = VirtualPathUtility.ToAbsolute("~/api/");
       return request.Uri.LocalPath.StartsWith(apiPath);
    }
3
  • 1
    first parameter of .fail() is a jqXHR, It has status property Commented Jan 18, 2017 at 9:06
  • Shouldn't the status code returned in this case be Forbidden (403) rather than Unauthorized (401) which is more meaningful - return new HttpResponseMessage(HttpStatusCode.Forbidden)? en.wikipedia.org/wiki/…. - Not sure whether the middleware will handle 403 as well though.. Commented Jan 18, 2017 at 11:15
  • @Developer good call. If you add your comment as solution i will mark it as solution Commented Jan 18, 2017 at 11:48

4 Answers 4

1

The correct way would be to use the error callback

$.ajax({
            url: "api/file/2",
            method: "POST"
        })
        .success(function () {

            alert("File has been deleted.");
        })
        .error(function (xhr, textStatus, errorThrown) {
            alert("Unable to delete file");

        });

See the docs here

Sign up to request clarification or add additional context in comments.

Comments

1

I came across the issue where I am using cookie authentication in .NET Core 5, yet once the user is authenticated, everything BUT any initial AJAX request in the application works.

Every AJAX request would result in a 401. Even using the jQuery load feature would result in a 401, which was just a GET request to a controller with the [Authorize(Role = "My Role")]

However, I found that I could retrieve the data if I grabbed the URL directly and pasted it in the browser. Then suddenly, all my AJAX worked for the life of the cookie. I noticed the difference in some of the AJAX posts. The ones that didn't work used AspNetCore.AntiForgery in the headers, whereas the ones that did use AspNetCore.Cookies that authenticated.

My fix was to add a redirect in the OnRedirectToLogin event under cookie authentication. It works for all synchronous and asynchronous calls ensuring that AJAX redirects to the login page and authenticates as the current user. I don't know if this is the proper way to handle my issue, but here is the code.

I should mention that all of the AJAX code worked perfectly in my .NET 4 web application. When I changed to 5, I experienced new issues.

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(o => {
                  o.LoginPath = "/Account/Login";
              o.LogoutPath = "/Account/Logout";
              o.AccessDeniedPath = "/Error/AccessDenied";
              o.SlidingExpiration = true;
              //add this to force and request to redirect (my purpose AJAX not going to login page on request and authenticating)
              o.Events.OnRedirectToLogin = (context) => { 
                context.Response.Redirect(context.RedirectUri);
                return Task.CompletedTask;
              };
            });

Comments

0

Shouldn't the status code returned in this case be Forbidden (403) rather than Unauthorized (401) which is more meaningful - return new HttpResponseMessage(HttpStatusCode.Forbidden)? 401 vs 403

Status codes 401 (Unauthorized) and 403 (Forbidden) have distinct meanings.

A 401 response indicates that access to the resource is restricted, and the request did not provide any HTTP authentication. It is possible that a new request for the same resource will succeed if authentication is provided. The response must include an HTTP WWW-Authenticate header to prompt the user-agent to provide credentials. If valid credentials are not provided via HTTP Authorization, then 401 should not be used.[2]

A 403 response generally indicates one of two conditions:

  • Authentication was provided, but the authenticated user is not permitted to perform the requested operation.
  • The operation is forbidden to all users. For example, requests for a directory listing return code 403 when directory listing has been disabled.

Comments

0

You can extract the information from the XMLHttpRequest object supplied as first parameter of .fail(function(jqXHR) {}). The object has status property which returns the numerical status code. The status codes returned are the standard HTTP status codes.

$.ajax({
    url: "api/file/2",
    method: "POST"
})
.fail(function (jXHR) {
    if (jXHR.status == 401) {
        alert('Unauthorised')
    }   
});

3 Comments

please see updated post. I've noticed a call to return Unauthorized(); always returns status 200 so the fail function never gets called?
@adam78, if it returns 200 then its not Unauthorized, You can test .always() which as name suggest will always execute
see my updated post. The web method is definately returning Unauthorized becuase when i reload the page the file is still there. If i attempt the same with a user that created the file then it works as expected i.e. it deletes the file.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.