Search code examples
jqueryajaxcorsasp.net-core-webapi

How do I resolve a CORS issue in an Ajax POST to ASP.NET Core 6.0 Web API?


I have an ASP.NET website (not a web app) that makes an Ajax POST call using JQuery 2.1.4 to an ASP.NET Core 6.0 Web API.

When I make the call to the method I am getting a CORS error. Yet, when I make a GET Ajax request to a different method in the same controller, the request is successful.

My Ajax POST:

function insertLNItemStorageRequirement() {
  var tempLNStorage = {
    TItem: storageTempReq.t_item,
    TSrq1: storageTempReq.t_srq1,
    TSrq2: storageTempReq.t_srq2,
    TSq27: storageTempReq.t_sq27,
    TStmp: storageTempReq.t_stmp,
    TRcdVers: 0,
    TRefcntd: 0,
    TRefcntu: 0,
  };

  $.ajax({
    type: "POST",
    url: commonAPIURL + "api/LN/InsertItemStorageRequirement",
    data: JSON.stringify(tempLNStorage),
    contentType: "application/json; charset=utf-8",
    //dataType: "json",
    xhrFields: { withCredentials: true },
    success: function (response) {},
    failure: function (response) {
      alert(response.responseText);
    },
    error: function (response) {
      alert(response.responseText);
    },
  });
}

Here is the ASP.NET Core Web API method being called:

[HttpPost("InsertItemStorageRequirement")]
[Produces(typeof(IActionResult))]
public IActionResult InsertItemStorageRequirement([FromBody] Ttccgs016424 itemStorageReq)
{
    Ttccgs016424 newItemStorageReq = _LN.Ttccgs016424s.FirstOrDefault(s => s.TItem == itemStorageReq.TItem);

    if (newItemStorageReq == null)
    {
        newItemStorageReq = new Ttccgs016424()
        {
            TItem = itemStorageReq.TItem,
            TSrq1 = itemStorageReq.TSrq1,
            TSrq2 = itemStorageReq.TSrq2,
            TSq27 = itemStorageReq.TSq27,
            TStmp = itemStorageReq.TStmp,
            TRcdVers = itemStorageReq.TRcdVers,
            TRefcntd = itemStorageReq.TRefcntd,
            TRefcntu = itemStorageReq.TRefcntu
        };

        try
        {
            _LN.Ttccgs016424s.Add(newItemStorageReq);
            _LN.SaveChanges();
        }
        catch (Exception ex)
        {
            return BadRequest(ex.Message);  
        }

        return StatusCode(StatusCodes.Status201Created);
    }
    else
    {
        return StatusCode(StatusCodes.Status412PreconditionFailed, "Item Storage Requirement already exists.");
    }
}

Here is my CORS configuration in my startup.cs for the API (note that my origin is part of the origins array):

services.AddCors(setup => {
    setup.DefaultPolicyName = "open";
    setup.AddDefaultPolicy(p => {
        p.AllowAnyHeader();
        p.WithMethods("OPTIONS", "GET", "POST", "PUT", "DELETE");
        p.WithOrigins(origins);
        p.AllowCredentials();
     });
});

Request headers sent:

OPTIONS /api/LN/InsertItemStorageRequirement HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: POST
Connection: keep-alive
Host: localhost:31227
Origin: http://localhost:14612
Referer: http://localhost:14612/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Mobile Safari/537.36 Edg/128.0.0.0

Response:

HTTP/1.1 401 Unauthorized
Cache-Control: private
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/10.0
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
X-Powered-By: ASP.NET
Date: Wed, 11 Sep 2024 23:43:25 GMT
Content-Length: 5995

I've tried changing the AJAX call to use credentials: 'include'.

And tried changed the

data: '{itemStorageReq: "' + JSON.stringify(tempLNStorage) + '"}'

Neither have resulted in a successful POST to the API.

GET requests to a different method in the same controller of the API are successful.

I suspect that this isn't truly a CORS issue, but rather a data mismatch between what the Ajax is sending and what the API is expecting.

Any suggestions on how to troubleshoot this issue?

Update IIS has the CORs module installed: Picture of IIS modules on the REST API

Update 2 9/23/24

Moved my app.UseCors() to between app.UseRouting() and app.UseEndpoints() before calling authenticate and authorize in the configure() of startup.cs.

app.UseRouting();

app.UseCors("open");

app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
        endpoints.MapControllers();
});

Also, I am using endpoint routing, which according to MS, means that the CORS Module won't automatically respond to OPTIONS requests: https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-6.0#httpoptions-attribute-for-preflight-requests.

In the same link MS recommends writing code in your endpoint to authorize the OPTIONS. So something like this?

[HttpOptions("InsertItemStorageRequirement")]
public IActionResult PreFlightRoute() {
       return NoContent();
}

Solution

  • I ended up modifying permissions on IIS according to this tutorial: https://techcommunity.microsoft.com/t5/iis-support-blog/putting-it-all-together-cors-tutorial/ba-p/775331.

    Enabled anonymous for the API. Then added authorization rules to limit Anonymous requests to OPTIONS verbs only. Finally, I added another authorization rule to allow all users from the 'USERS' group.

    The article above is dated, describing these changes as being superseded by the IIS CORS module, which has been implemented on my API since the beginning, but which failed to act on OPTIONS sent to the API. The CORS module, with a properly configured pipeline, should intercept OPTIONS requests without having to enable Anonymous Authentication for the entire API.

    I did follow the MS advice for properly arranging middleware in a .NET Core app: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-6.0#middleware-order-1, but it did not result in a successful POST request for me.

    I also had attempted to implement these suggestions from MS for when you are using endpoint routing (which I am): https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-6.0#httpoptions-attribute-for-preflight-requests. They also did not allow OPTIONS requests to pass.