6

I have a Blazor WASM app and a Web Api to get called by Blzor via HttpClient. Both programs run on the same machine (and also in production environment which should not be to exotic for a small business application!).

Calling the Web Api from the Blazor client result in a client CORS exception

Access to fetch at 'http://localhost:4040/' from origin 'https://localhost:5001' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

which is the expected behavior for this case.

In an former api project I developed in PHP, that had the same client behavior, I can bypass the CORS exception by simply setting the response header e.g.

$response = new Response;
$response->setState(false, $route->command);
...
header("Access-Control-Allow-Origin: *");
echo $response;

Now I want this in my .net 5.0 Web Api. I found different docs in the internet to cope with that like in

https://docs.microsoft.com/de-de/aspnet/core/security/cors?view=aspnetcore-5.0 https://www.c-sharpcorner.com/article/enabling-cors-in-asp-net-core-api-application/ https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.cors.infrastructure.corspolicybuilder.withorigins?view=aspnetcore-5.0

and implemented it in my api

    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()
                        .AddSwaggerGen(c => c.SwaggerDoc("v1", new OpenApiInfo { Title = "api", Version = "v1"}) )
                        .AddControllers()
                        ;
        //---------------------------------------------------------------------

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure
                    (
                        IApplicationBuilder app,
                        IWebHostEnvironment env
                    )
                    =>  app.
                        apply( _ =>
                        {
                            if (true) //(env.IsDevelopment())
                            {
                                app
                                .UseDeveloperExceptionPage()
                                .UseSwagger()
                                .UseSwaggerUI( c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "api v1") );
                            }
                        })
                        .UseCors( cors =>
                            cors
                            .AllowAnyHeader()
                            .AllowAnyMethod()
                            .SetIsOriginAllowed( _ => true )
                            .AllowCredentials()
                        )
                        .UseHttpsRedirection()
                        .UseRouting()
                        .UseAuthorization()
                        .UseEndpoints( e => e.MapControllers() )
                        ;
        //---------------------------------------------------------------------
    }

Even tried to set the Response Header in the ApiController

    [Route("/")]
    [ApiController]
    public sealed class ServertimeController : ControllerBase
    {
        //[EnableCors("AllowOrigin")]
        [HttpGet]
        public Servertime Get() {

            Response.Headers.Add("Access-Control-Allow-Origin", "*");
            Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT");

            return servertime();
        }
    }

The Blazor WASM client looks like

    private async void onClick()
    {

        var response = await httpClient.GetFromJsonAsync<Servertime> ("http://localhost:4040");
        message = response.servertime;
        this.StateHasChanged();
    }

But it still results in the client CORS exception. To bypass this for development I use the browser extension “CORS Unblock”, but this is not an option for deployment.

What would be the correct approach to avoid Blazor client exception, what do I miss?

tomschrot
  • 89
  • 1
  • 3
  • In the hosted project template both the app and *service* run from the same port, not just the same machine. In your case you're using two different ports which is no different than using two different machines as far as CORS is concerned. – Panagiotis Kanavos Nov 16 '20 at 13:02
  • Do you really want to host the app and API on separate ports? In that case you'll have to [configure CORS on the Web API to allow cross-origin calls](https://github.com/dotnet/AspNetCore.Docs/tree/master/aspnetcore/blazor/common/samples/5.x/BlazorWebAssemblySample). This is explained in the docs [here](https://docs.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/additional-scenarios?view=aspnetcore-5.0#cross-origin-resource-sharing-cors) – Panagiotis Kanavos Nov 16 '20 at 13:09
  • The *correct* way to handle this is to add the origins, not disable ports. It's very easy to do so, use `policy.WithOrigins("http://localhost:5000", "https://localhost:5001")` in your CORS configuration. As for `In an former api project I developed in PHP,` hackers read SO too and now know about one more vulnerability they can try against a specific target – Panagiotis Kanavos Nov 16 '20 at 16:53

2 Answers2

5

@Dmitriy Grebennikov answer is also valid but it needs a little bit improvements to make this more secure.

in ConfigureServices of Startup.cs add the following code before services.AddControllers();

services.AddCors(options =>
            {
                options.AddDefaultPolicy(builder => 
                builder.WithOrigins("https://localhost:44338")
                       .AllowAnyMethod()
                       .AllowAnyHeader());
            }); 

Make sure that your url should not end with /.


You can pass many url to WithOrigins method. finally in the Configure method of Startup.cs add the following line before app.UseAuthorization(); and after app.UseRouting();

app.UseCors();

The code should be working now.

Aay Que
  • 801
  • 6
  • 14
  • seems good to me, this answer is also in accord with the documentation https://docs.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-5.0 – Francesco May 07 '21 at 08:06
  • Totally agree with you. I had to add a comment that this solution is only for development purpose. You shouldn't use it in production. – Dmitriy Grebennikov May 28 '21 at 09:40
2

Just add this to ConfigureServices method:

services.AddCors(options =>
        {
            options.AddPolicy("DevCorsPolicy", builder =>
            {
                builder
                    .AllowAnyOrigin()
                    .AllowAnyMethod()
                    .AllowAnyHeader();
            });
        });

And this to Configure method:

app.UseCors("DevCorsPolicy");
  • 1
    Disabling CORS is equivalent to telling someone to disable security to get over security restrictions. A very bad answer. Especially when the correct way to do this is to use `WithOrigins()` and the list of allowed origins, eg `policy.WithOrigins("http://localhost:5000", "https://localhost:5001")` – Panagiotis Kanavos Nov 16 '20 at 13:03
  • @PanagiotisKanavos only for development purposes. It's not about production deployment. And the question implied it. – Dmitriy Grebennikov Nov 16 '20 at 13:08
  • Doing this correctly only needs a few more characters – Panagiotis Kanavos Nov 16 '20 at 13:08
  • @PanagiotisKanavos honestly, i don't even get why cors is enabled by default, in most projects i worked on it didn't create security... but only work and overhead. it's only used by browsers, in the first place and can easily be spoofed and i did see some projects where the programmer implemented major security flaws, cause they believed they could rely on it – Patrick Beynio Nov 16 '20 at 13:36
  • 1
    @PatrickBeynio *browsers* require CORS. Without it, the *browser* refuses to call an API for security reasons. CORS prevents script injection attacks that would call the hacker's servers to either send data or receive more scripts to execute. `only used by browsers` that's not only. The whole point is to protect the end users. `it can be spoofed` if the web site developer is sloppy. `where the programmer implemented major security flaws` why use seat belts then? They can be taken off. – Panagiotis Kanavos Nov 16 '20 at 14:17
  • @PanagiotisKanavos "Without it, the browser refuses to call an API for security reasons" i implemented a http server with tls and without cors and it's still callable. "CORS prevents script injection attacks" no, only the calls the injected scripts attempt. "`only used by browsers` that's not only" can you give at least one example? i don't know any other uses. "it can be spoofed if the web site developer is sloppy", no the problem would need to be in the browser! but, they're usually not spoofed through browsers... – Patrick Beynio Nov 16 '20 at 15:27
  • @PatrickBeynio yes, if you start removing security features they won't work. Just like using a belt buckle to silence the seat belt alarm and turning off the airbag. Since neither of them gives you 100% crash safety, why enable any of them? I'm sure the police will be persuaded by that argument during a traffic stop. As for `that's not only.` the phrase means that browser security isn't an `only`. It's a primary concern for every vendor - both browser and service vendors – Panagiotis Kanavos Nov 16 '20 at 16:47
  • @Dimitri Sorry, but that is exactly what Im doing. And it is not working! – tomschrot Nov 16 '20 at 16:59
  • @PanagiotisKanavos maybe, if you didn't use 80% metaphors i could tell what you're talking about – Patrick Beynio Nov 16 '20 at 18:06
  • I'm saying that disabling a security feature only because one thinks it doesn't solve all security issues is dangerous. – Panagiotis Kanavos Nov 16 '20 at 18:23
  • So this answer is fine for testing purpose. I found a more comprehensive answer in another post that shows how to secure this as needed: https://stackoverflow.com/a/45599532/10787774 – Jason D Jan 25 '21 at 19:32