jysummers
jysummers

Reputation: 789

Why Cross-Origin Resource Sharing (CORS) does not block on server side?

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:

enter image description here

But if you put a breakpoint on the api controller, it still hits the Post() method:

enter image description here

Upvotes: 0

Views: 1955

Answers (1)

froitag
froitag

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.

https://developer.mozilla.org/docs/Web/HTTP/CORS

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

Related Questions