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:
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();
}
2
Answers
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.
The error message you shared contains
WWW-Authenticate: NTLM
so that the preflight request which doesn’t contains windows auth credential shall be blocked and get 401 error.Since the preflight request can’t get the desired CORS policy back, the POST request gets CORS error is the expected behavior. Similar to this ticket.
If you are working locally, the workaround is allowing anonymous. In your API project
launchsettings.json
, you might need to set"anonymousAuthentication": true
like below. And if you are hosting your app in IIS now, you can use CORS module just like the document I shared above said.