Reputation: 789
I created 2 projects: Client and Server.
Client is a Razor web app that contains javascript on the index page for calling the api. It is hosted under http://localhost:8000.
Index.cshtml
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://learn.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>
<div class="container">
<div class="row">
<div class="col-6">
<button id="sender-get">GET</button>
<div id="content-get"></div>
</div>
<div class="col-6">
<button id="sender-post">POST</button>
<div id="content-post"></div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.0/js/bootstrap.min.js" integrity="sha256-oKpAiD7qu3bXrWRVxnXLV1h7FlNV+p5YJBIr8LOCFYw=" crossorigin="anonymous"></script>
<script>
$(document).ready(() => {
$('#sender-get').click(() => {
$.get("http://localhost:9000/weatherforecast")
.done(() => {
$('#content-get').text('done');
})
.fail(() => {
$('#content-get').text('fail');
});
});
$('#sender-post').click(() => {
$.post("http://localhost:9000/weatherforecast")
.done(() => {
$('#content-post').text('done');
})
.fail(() => {
$('#content-post').text('fail');
});
});
});
</script>
Server is an ASPNET Core (3.1) Web Api with the weatherforecast template. It is hosted under http://localhost:9000. It uses the CORS middleware and is configured to accept request with origin http://localhost:5000.
WeatherForecastController.cs
namespace Server.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
[HttpPost]
public IEnumerable<WeatherForecast> Post()
{
return null;
}
}
}
Startup.cs
namespace Server
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.WithOrigins("http://localhost:5000");
});
});
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
As I click on POST, the request fails, which is a correct response:
But if you put a breakpoint on the api controller, it still hits the Post() method:
Upvotes: 0
Views: 1955
Reputation: 185
The controller endpoint being hit is expected behavior.
CORS restrictions are enforced on browser-side and never on server-side. This is also true for the ASP.NET Core CORS middleware that you mentioned. The middleware's responsibility is solely to instruct the browser not to send disallowed requests in the first place, if possible. However, some "simple" requests (like some very simple GETs and POSTs) will always make it to the server - this is what you observed.
CORS might sound tricky at first, but as it is with all things in life, once you take a look behind the curtain, it all makes sense.
Cross-Origin Resource Sharing (CORS) is a mechanism that uses additional HTTP headers to tell browsers to give a web application running at one origin, access to selected resources from a different origin. A web application executes a cross-origin HTTP request when it requests a resource that has a different origin (domain, protocol, or port) from its own.
For simple requests (like most HEAD/GET
but also some simple POST
, see https://developer.mozilla.org/docs/Web/HTTP/CORS#Simple_requests for details), the browser just executes the requests and inspects the CORS headers in the response (like Access-Control-Allow-Origin
) to determine whether the request was allowed or whether the result should be discarded.
For pre-flighted requests (like PUT/PATCH/DELETE
but also GET/POST
with non-standard headers or content types, see https://developer.mozilla.org/docs/Web/HTTP/CORS#Preflighted_requests for details), the browsers issues a so called pre-flight request with the OPTION
http verb upfront to figure out whether the endpoint allows cross origin requests or not.
The browser does upfront pre-flight requests for any requests that might be altering data on the server exactly because of the behavior that you observed. If it wouldn't use a harmless OPTION
upfront, the server would just delete the corresponding requests when it receives a DELETE
request although the request should have been rejected by the server's CORS policies.
However, the browser does not do pre-flight requests for simple requests like most GET
because those are expected to harmless. That is why your breakpoint was still hit but the response was then discarded by the browser. That's also one of the reasons why you should never alter data in GET
requests but use dedicated verbs like PUT/PATCH/DELETE
for that :)
Upvotes: 3