0

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

jysummers
  • 649
  • 4
  • 16
  • *“I did not add a CORS middleware which, I believe, by default should not allow cross origin request.”* — To be clear about something: Do you understand that by default browsers block frontend JavaScript code from accessing cross-origin requests? That’s where the blocking occurs: in browsers, not in servers. CORS is a way to tell browsers to un-block access to responses. So adding CORS middleware is a way to tell browsers to allow access to responses, right? So if you haven’t CORS-enabled the server the request was sent to, it’s not going to work. You need to add CORS middleware, don’t you? – sideshowbarker May 09 '20 at 10:49
  • Thanks for this. So, from what you said: by default browsers block frontend JavaScript code from accessing cross-origin requests That means the browser is vulnerable? Because the frontend app is hosted on port 8000 and the web api is hosted on port 9000. Requests from the frontend should be block by the browser. However, in the case I presented above, the web api still accepts the frontend request. – jysummers May 09 '20 at 13:09
  • Browsers don’t block the requests from being sent. Instead, browser just block your frontend JavaScript code from access to the responses. See the answer at https://stackoverflow.com/a/44198143/441757 – sideshowbarker May 09 '20 at 23:12
  • I added a CORS middleware but it still hits the controller (I have updated my question above) – jysummers May 11 '20 at 04:05

1 Answers1

3

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 :)

Community
  • 1
  • 1
froitag
  • 177
  • 1
  • 1
  • 9
  • Well I just created this as a sample. I am actually having issues on POST requests that I am working on. But thank you for reminding me about pre-flight requests. =) – jysummers May 09 '20 at 13:18
  • Maybe I misunderstood your issue then, could you elaborate a bit? The GET request is expected to make it to the server, no matter if you're blocking CORS requests (e.g. by NOT using a CORS middleware) or allowing them. POST requests, however, should not make it to the server. Instead, a pre-flight OPTIONS should be triggered, the server will not respond to it and the browser knows that it cannot do the POST. – froitag May 09 '20 at 13:31
  • 1
    Not all POST requests trigger a preflight. See the answer at https://stackoverflow.com/a/44198143/441757 – sideshowbarker May 09 '20 at 23:27
  • Good point @sideshowbarker, I edited the answer to clarify that some very basic POST are also considered simple requests not triggering a pre-flight. This is most likely also what OP observed when saying that his breakpoint is also being hit with a POST. If the POST is done without special headers and either with a simple plain text or form data body, this will directly hit the endpoint without triggering a pre-flight. – froitag May 10 '20 at 00:43
  • I added a CORS middleware but it still hits the post method. – jysummers May 11 '20 at 04:10
  • @gulp, most likely your POST is considered a simple request because it doesn't use any headers (apart the default ones) and does not have a body (see the answer for more details and a link). Therefore, it is still being executed (without a pre-flight) but the result is discarded. This is expected since CORS is enforced on browser-side and not server-side. If Your POST would e.g. have a JSON body or use some header like `Authorization`, it would not be considered a simple request anymore and the endpoint would not be called - but a pre-flight OPTIONS would instead. – froitag May 11 '20 at 08:58
  • Even if I dont use any header, so long as I enabled CORS, it should block the request because it is coming from untrusted source. – jysummers May 12 '20 at 06:10
  • 3
    With CORS, requests are never blocked on server-side. This is also true for the ASP.NET Core CORS middleware. It will only instruct the browser to not send the requests in the first place if possible. However, as mentioned in the answer, some "simple" requests will always make it to the server - you need to be aware of that. The browser will drop the response afterwards but the request is still being made. – froitag May 12 '20 at 14:32
  • I see. will have to add blocking codes then. Thanks @froitag! =) – jysummers May 15 '20 at 02:06
  • @gulp you are welcome! I edited the answer to make it clear that CORS is not enforced on server-side. Would appreciate if you could mark the answer as accepted answer! :) – froitag May 16 '20 at 00:02