18

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://learn.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://learn.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?

Kzryzstof
  • 7,688
  • 10
  • 61
  • 108
tomschrot
  • 219
  • 1
  • 2
  • 4
  • 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://learn.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

8 Answers8

23

@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.

Abdul Qayyum
  • 1,024
  • 7
  • 15
  • seems good to me, this answer is also in accord with the documentation https://learn.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. – Dmitry Grebennikov May 28 '21 at 09:40
7

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");
  • 3
    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
  • 2
    @PanagiotisKanavos only for development purposes. It's not about production deployment. And the question implied it. – Dmitry 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
3

For a solution in Blazor WASM, NOT on Serverside, this code snippet

Microsoft.AspNetCore.Components.WebAssembly.Http.WebAssemblyHttpRequestMessageExtensions.SetBrowserRequestMode(
    httpRequestMessage, 
    Microsoft.AspNetCore.Components.WebAssembly.Http.BrowserRequestMode.NoCors
);

will tell the local browser, which runs the local PWA, to not send CORS with the Request.

Code-Example (with call and used namespaces)

HttpRequestMessage httpRequestMessage = new HttpRequestMessage{
    RequestUri = new Uri("[Adress]"),
    Content = new StringContent(),
    Method = HttpMethod.Post };

WebAssemblyHttpRequestMessageExtensions.SetBrowserRequestMode(httpRequestMessage, BrowserRequestMode.NoCors);

HttpResponseMessage response = client.SendAsync(httpRequestMessage).Result;

--> the Version from "Dmitry Grebennikov" works. In my version, the Post isn`t async and the PWA throw an error.

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Apr 28 '22 at 12:52
0
var client = new HttpClient();

var request = new HttpRequestMessage
{
    Method = new HttpMethod("GET"),
    RequestUri = new Uri($"{site.BaseUrl}robots.txt")
};

request.Headers.Add("Accept", "text/plain");
request.SetBrowserRequestMode(BrowserRequestMode.NoCors);

var response = await client.SendAsync(request);

var txt = await response.Content.ReadAsStringAsync();

References

gjmwolmarans
  • 285
  • 2
  • 11
  • 1
    Its not work, query real send to server, and in browser, we can see response, but httpresponse will be with 0 status code, and empty content :( – Maxumka Sep 30 '21 at 10:25
0

The answer from Dmitry Grebennikov does not fully work for me in a .NET 7 standalone WebAssembly project, but it was part of the solution.

var request = new HttpRequestMessage(method, uri);

We need to first set:

Microsoft.AspNetCore.Components.WebAssembly.Http.WebAssemblyHttpRequest
MessageExtensions.SetBrowserRequestMode(request, Microsoft.AspNetCore.Components.WebAssembly.Http.BrowserRequestMode.NoCors);

Now we need to add a CORS allow header:

request.Headers.Add("Access-Control-Allow-Origin","*");
request.Headers.Add("Access-Control-Allow-Headers","Origin, X-Requested-With, Content-Type, Accept");

Now I got CORS working with HttpClient. :)

Standalone Blazor WebAssembly does not support .AddCors and it will not work, according to Microsoft answers on this question.

mfluehr
  • 2,832
  • 2
  • 23
  • 31
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – KiynL Mar 26 '23 at 11:43
0

After much hair pulled out what helped me was to call the server (localhost at development time) on http instead of https and I needed to add Access-Control-Request-Headers to the RequestHeader with value content-type. The server side was configured properly, but I still got a CORS error.

This is my method:

public async Task<T?> Post<T>(object? data)
    var client = new HttpClient()
    {
        BaseAddress = new Uri("http://localhost/MyApi/api/postData")
    };

    var requestContent = new StringContent(
        JsonConvert.SerializeObject(data),
        UTF8Encoding.UTF8,
        new MediaTypeHeaderValue("application/json"));

    var httpRequestMessage = new HttpRequestMessage
    {
        RequestUri = new Uri($"{client.BaseAddress}"),
        Content = requestContent,
        Method = HttpMethod.Post,
        Headers =
        {
            {"accept", "application/json"},
            {"Access-Control-Request-Headers", "content-type"}
        }
    };

    var result = await client.SendAsync(httpRequestMessage);
    
    if (!result.IsSuccessStatusCode)
        return default;

    T? content = default;
    try
    {
        content = await result.Content.ReadFromJsonAsync<T>();
    }
    catch (Exception ex)
    {

    }

    return content;
}
morcibacsi
  • 43
  • 7
0

You could make the solution proposed by @Abdul Qayyum production ready using environment configuration to get the list of sites allowed to access the API.

I already used the following code in an existing product:

var corsAllowedOrigins = builder.Configuration["Cors:AllowedOrigins"];
if (!string.IsNullOrEmpty(corsAllowedOrigins)) {
    var origins = corsAllowedOrigins.Split(',', ';');
    Log.Information("Setting Cors Origins: {orgins}", (object)origins);
    builder.Services.AddCors(options => {
        options.AddPolicy("CorsPolicy", builder => {
            builder.WithOrigins(origins)
                .AllowCredentials()
                .AllowAnyMethod()
                .AllowAnyHeader();
        });
    });        
} else {
    Log.Warning("Cors policy not specified, client can't connect to API");
    // or add a default policy
}

// ... and then ...
var app = builder.Build();
app.UseCors("CorsPolicy");

With this code you could use appsettings.json or Cors__AllowedOrigins environment variable (this works with docker).

0

Interestingly enough I had a similar problem my CORS setup in my Program.cs seemed fine but the actual problem was the app.UseHttpsRedirection() middleware. If your client app is running on HTTP instead of HTTPS this middleware will break CORS behavior.

Reference: HTTP redirection to HTTPS causes ERR_INVALID_REDIRECT on the CORS preflight request.

The solution would be to remove the middleware if it is not really needed and Microsoft docs recommend: API projects can reject HTTP requests rather than use UseHttpsRedirection to redirect requests to HTTPS.

I hope this helps someone.

Khutso
  • 181
  • 1
  • 4