In this post, we are going to talk about enabling CORS in ASP.NET Core Web Applications.
Before we understand how to enable CORS in ASP.NET Core applications, we need to understand an important concept in the web application security model called the Same-Origin Policy.
VIDEO: Implement CORS in ASP.NET Core Web API.
What Is CORS?
Let’s learn what the term same-origin actually means first.
About Same-Origin and What It Means
Two URLs are considered to have the same origin if they have the same URL scheme (HTTP or HTTPS), same hostname (domain name), and same port number (the endpoint at which applications talk to each other).
What Is the Same-Origin Policy?
The Same-origin policy states that a Web browser will only allow communication between two URLs if they belong to the same origin. That is, the client app (https://example.com
) cannot communicate with the server app (https://example.net
) as they belong to a different origin. This policy exists to isolate potentially malicious scripts from harming other documents on the web.
But there are some cases where Cross-Domain scripting is desired and the idea of the open web makes it compelling.
Cross-Origin Resource Sharing is a mechanism to bypass the Same-Origin policy of a Web browser. Specifically, a server app uses additional HTTP headers to tell a browser that it is fine to load documents (from its origin) in a few selected client apps (of different origin).
But how does CORS actually work?
Let’s have a look at the image. There is a client app (https://example.com
) sending a GET request to a server app (https://example.net
) for some resource:
All modern browsers set the Origin
header automatically, which indicates the domain of the site is making the request.
If the server allows Cross-origin requests from the Origin (https://example.com
), it sets the Access-Control-Allow-Origin
header with its value matching the origin header’s value from the request.
In another way, if the server doesn’t include this header, the request fails. The browser shall receive the response data, but this data shall not be accessible to the client.
That’s how a simple CORS request works.
Preflight Requests
Sometimes, instead of a simple GET request, a client may need to send requests like PUT, DELETE, etc. For such requests, the browser sends an additional request (an OPTIONS request) called a Preflight request. This is done just before the actual request to make sure that the original request succeeds. If it does, the browser sends the actual request.
This diagram illustrates such a scenario:
Actual request
Creating an Application Without CORS
In the demo, let’s create two projects. The first one is going to be a Web API project, and the second one is going to be a Blazor WebAssembly project.
Let’s create a Web API project that will be our server. Once we create this project, we are going to modify the launchSettings. json
file:
{ "profiles": { "CorsServerApp": { "commandName": "Project", "dotnetRunMessages": "true", "launchBrowser": false, "launchUrl": "weatherforecast", "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } }
As you can see, this app will run on the https://localhost:5001 URI.
Next, let’s create a Client app. We are going to create a new Blazor WebAssembly standalone project. Of course, we have to modify the launchSettings.json
file as well:
{ "profiles": { "CorsClientApp": { "commandName": "Project", "dotnetRunMessages": "true", "launchBrowser": true, "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", "applicationUrl": "https://localhost:5011;http://localhost:5010", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } }
Okay. We have both a client and the server app running and they belong to different origins. The server app will run on port 5001 and the client app on port 5011.
To make cross-origin requests, we first have to modify the Program.cs
file of the client’s app:
using CorsClientApp; var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); builder.Services.AddScoped(_ => new HttpClient { BaseAddress = new Uri("https://localhost:5001") }); await builder.Build().RunAsync();
Here, we just set up the base address for the HttpClient, which is the URI of our Web API.
Then, we have to open the Pages/FetchData.razor
file, and modify the @code part:
@code { private WeatherForecast[] forecasts; protected override async Task OnInitializedAsync() { forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("/weatherforecast"); } ... }
Since we already modified the base address of the HttpClient, by adding this change, we want to send our GET request to the https://localhost:5001/weatherforecast
URI, which is the exact address of the GET action inside the WeatherForecast controller in the server app.
Since we haven’t enabled CORS on the server app, the server app doesn’t set any headers and the browser rejects the response.
So, if we start both applications, navigate to the client app, and open the Fetchdata page, we are going to see the error message:
So, we can see that the request is blocked by CORS policy and our client app is not allowed to fetch the data from the required resource.
Enabling CORS in ASP.NET Core Middleware
Now that we have seen the Same-Origin policy in action, let’s see how we can enable CORS in ASP.NET Core.
To do that, let’s open the Program.cs
file in the server app and modify it:
var builder = WebApplication.CreateBuilder(args); const string policyName = "CorsPolicy"; builder.Services.AddCors(options => { options.AddPolicy(name: policyName, builder => { builder.AllowAnyOrigin() .AllowAnyHeader() .AllowAnyMethod(); }); }); builder.Services.AddControllers(); ... app.UseCors(policyName); app.UseAuthorization(); app.MapControllers(); app.Run();
First, we create a private variable to hold the name of our CORS policy. Then, we call the AddCors
method that adds cross-origin resource-sharing services to the service collection. This method accepts an Action delegate as a parameter where we can configure the CORS options. For the CORS options, we call the AddPolicy method, which adds a new policy to the configuration and also accepts an Action delegate as a parameter where we can set up the methods to build our policy.
With this policy, we enable access from any origin AllowAnyOrigin
, allow any header in a request AllowAnyHeader
, and allow any method AllowAnyMethod
.
We can agree that this policy is not the strict one, but it is a basic setup that will remove the CORS issue for our client application.
Also, we call the UseCors
method and pass the policy name to add the CORS middleware to the application’s pipeline. We must call this method after the UseRouting
method and before the UseAuthorization
method.
Now, with these changes in place, we can start both of our apps and navigate to the FetchData
page:
There we go. We have enabled CORS in our application and removed the issue we were facing before.
Default Policies for CORS in ASP.NET Core
In the case where we don’t want to use multiple named policies but a single default policy, we can replace the AddPolicy
method with the AddDefaultPolicy
method:
builder.Services.AddCors(options => { options.AddDefaultPolicy(builder => { builder.AllowAnyOrigin() .AllowAnyHeader() .AllowAnyMethod(); }); });
As you can see, we are not providing the name of the policy. Additionally, we don’t have to provide the policy name in the UseCors
method:
app.UseAuthorization(); app.UseCors(); app.MapControllers();
For the rest of the article, we are going to stick with the named CORS policy configuration.
Enabling CORS in ASP.NET Core With Attributes
Instead of enabling CORS at the entire application level, we can also enable CORS at the controller level or the action level. To be able to do that, we have to use the [EnableCors]
attribute:
- We can use just the
[EnableCors]
attribute on top of the controller or the action, and it will implement a default CORS policy - Or we can use the
[EnableCors("Policy name")]
attribute, to apply a named CORS policy
By using the named policy with the [EnableCors]
attribute, we can apply different policies to different controllers or actions.
So, in our example, if we want, we can add one more named policy for the CORS configuration:
const string policyName = "CorsPolicy"; const string anotherPolicyName = "AnotherCorsPolicy"; builder.Services.AddCors(options => { options.AddPolicy(name: policyName, builder => { builder.AllowAnyOrigin() .AllowAnyHeader() .AllowAnyMethod(); }); options.AddPolicy(name: anotherPolicyName, builder => { builder.AllowAnyOrigin() .AllowAnyHeader() .AllowAnyMethod(); }); });
Also, we have to modify:
app.UseAuthorization(); app.UseCors(); app.MapControllers();
This time, we are not specifying the policy name since we are using more than one policy in our application.
Finally, to see the [EnableCors]
attribute in action, we are going to modify the WeatherForecastController
:
[EnableCors("CorsPolicy")] [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(); } [EnableCors("AnotherCorsPolicy")] [HttpGet("anotherPolicyExample")] public IActionResult GetTempAboveLimit() { return Ok("This action is protected with another named CORS policy"); }
As you can see for the first action, we are enabling CORS using the old named policy – CorsPolicy. But for the other action, we are using a new policy – AnotherCorsPolicy.
Okay, now we know how to apply different policies while enabling CORS. However, it is not a good practice to use two different named policies that apply the same policy options. In this situation, only one policy would be sufficient.
So, let’s see how we can configure different policy options and make sense of using different named policies in our application.
Configuring CORS Policy
We can use various options while adding policies to the CORS configuration.
Right now, for both policies, we are allowing any origin, any header, and any method. But let’s say, we want to allow only our client application to access the first GET action from the WeatherForecast
controller, and some other client (on port 5021) to access the second action:
builder.Services.AddCors(options => { options.AddPolicy(name: policyName, builder => { builder.WithOrigins("https://localhost:5011") .AllowAnyHeader() .AllowAnyMethod(); }); options.AddPolicy(name: anotherPolicyName, builder => { builder.WithOrigins("https://localhost:5021") .AllowAnyHeader() .AllowAnyMethod(); }); });
This time, we are using the WithOrigins
method, which accepts a params string[] origins
as a parameter. This means, if we want to allow access to multiple origins for a single policy, we can just add multiple comma-separated URIs as an argument: WithOrigins("First URI", "Second URI")
.
Configuring CORS for Multiple Subdomains
One more thing. When we want to allow access for a client that has a main domain and multiple subdomains, we don’t have to add all the URIs with subdomains as arguments. What we can do is use the wildcard in the WithOrigins
method followed by the SetIsOriginAllowedToAllowWildcardSubdomains
method:
opt.AddPolicy(name: policyName, builder => { builder.WithOrigins("https://*.code-maze.com") .SetIsOriginAllowedToAllowWildcardSubdomains(); });
By using the SetIsOriginAllowedToAllowWildcardSubdomains
method, we allow origins to match a configured wildcard domain when evaluating if the origin is allowed.
Enabling CORS for Specific Methods
If we take a look at our controller, we can see that we have only GET actions inside. Well, we can specify that as well in the CORS configuration:
builder.Services.AddCors(options => { options.AddPolicy(name: policyName, builder => { builder.WithOrigins("https://localhost:5011") .AllowAnyHeader() .WithMethods("GET"); }); options.AddPolicy(name: anotherPolicyName, builder => { builder.WithOrigins("https://localhost:5021") .AllowAnyHeader() .WithMethods("GET"); }); });
Now, instead of AllowAnyMethod
, we are using WithMethods
, which accepts params string[] methods
as a parameter. Of course, this means if we want to allow access to multiple methods, we need to add them as multiple comma-separated strings: WithMethods("PUT", "DELETE", "GET")
.
Enabling CORS for Specific Headers
In the same way that we use the WithMethods
method to specify access for single or multiple methods, we can use the WithHeaders
method to allow specific headers to be sent in a CORS request:
opt.AddPolicy(name: anotherPolicyName, builder => { builder.WithOrigins("https://localhost:5021") .WithHeaders(HeaderNames.ContentType, HeaderNames.Accept) .WithMethods("GET"); });
Additionally, when we want to expose some headers, we can use the WithExposedHeaders
method. For example, in our Blazor WebAssembly Pagination with ASP.NET Core Web API article, we create a pagination functionality on the Web API’s side and add the required information inside the X-Pagination
header:
[HttpGet] public async Task<IActionResult> Get([FromQuery] ProductParameters productParameters) { var products = await _repo.GetProducts(productParameters); Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(products.MetaData)); return Ok(products); }
But for our client application to be able to access that information inside the header, we have to expose it in the CORS configuration:
opt.AddPolicy(name: anotherPolicyName, builder => { builder.WithOrigins("https://localhost:5021") .WithHeaders(HeaderNames.ContentType, HeaderNames.Accept) .WithMethods("GET") .WithExposedHeaders("X-Pagination"); });
And that’s it for our CORS configuration.
Additional read: You can see the CORS in action with .NET Core and Angular projects by reading the .NET Core tutorial.
Using CORS With Minimal API
So far, we have applied CORS policies to the controller’s action. We can use CORS with minimal API as well.
Let’s add a minimal API endpoint to our Program.cs
:
app.MapControllers(); var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; app.MapGet("/minimal/weatherforecast", () => { var forecast = Enumerable.Range(1, 5).Select(index => new WeatherForecast ( DateOnly.FromDateTime(DateTime.Now.AddDays(index)), Random.Shared.Next(-20, 55), summaries[Random.Shared.Next(summaries.Length)] )) .ToArray(); return forecast; }) .WithName("GetWeatherForecast") .WithOpenApi(); app.Run(); internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) { public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); }
Here, we define a new endpoint /minimal/weatherforecast
that would respond to the GET request and return some random weather data.
We can apply our (previously defined) policies to this endpoint:
app.MapGet("/minimal/weatherforecast", () => { var forecast = Enumerable.Range(1, 5).Select(index => new WeatherForecast ( DateOnly.FromDateTime(DateTime.Now.AddDays(index)), Random.Shared.Next(-20, 55), summaries[Random.Shared.Next(summaries.Length)] )) .ToArray(); return forecast; }) .WithName("GetWeatherForecast") .WithOpenApi() .RequireCors(policyName);
All we have to do is to use the RequireCors
method.
How to Disable CORS
Finally, a small note on disabling CORS. Sometimes we need to disable CORS on the action or the controller level.
To do that, we can use the [DisableCors] attribute:
[DisableCors] [HttpGet] public async Task<IActionResult> Get([FromQuery] ProductParameters productParameters) { var products = await _repo.GetProducts(productParameters); Response.Headers.Add("X-Pagination", JsonConvert.SerializeObject(products.MetaData)); return Ok(products); }
That’s it for now. Let’s recap what we’ve learned.
Conclusion
Think of CORS as a relaxation attempt to the more restrictive Same-Origin policy. On one side, there is a growing need for security on the web. And on the other, there is a need to integrate web services. CORS provides rich tools for our need for an open and secure web, and ASP.NET Core allows us to take advantage of CORS in our cross-platform web applications.
I am surprised your code works. You are using
UseHttpsRedirection()
beforeUseCors()
. That alone could cause your app to not work correctly.I also don’t see any explanation as to what happens with [HttpOptions] calls being made by the browser.
I must say that I am not sure why are you surprised about that. That’s the recommended order of using middleware by Microsoft. You can read more about that on our site https://code-maze.com/working-with-asp-net-core-middleware/, or if you like more, you can inspect Microsoft as well: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-6.0#middleware-order. So, I am not sure that you are correct about that.
thanks for such a nice explanation
You are most welcome.
I have the same setup as you (but using a WebMvc client) and added all the code you have here – UseCors() in Configure() and AllowAnyXXX() as AddDefaultPolicy() but my CORS errors in the browser persist.
I am at a loss, I even tried changing the order of where these methods are called but nothing prevents the CORS errors.
There must be something else that can prevent this from working.
Is it possible you left any configuration of the Blazor project out of the information here?
Hello Gerinomo. You can always inspect our source code, you have the link, and check it with your project.
I eventually figured it out – I didn’t have
UseAuthorization()
– since I am running a simple server on localhost I didn’t think it was required – but I now know it’s essential for CORS – even if you have no security or login or any real authorizationI am glad you solved your issue. Thank you for sharing the solution with us here.
Its very helpful and simple
Thanks !!
Thanks Javid!
I’l try your examle. But i think it will not work.
In Postman ACESS-Control-Allow -Origin was not set to “*”.
So I can only consume the REST-Service if Client and Service are on the localmachine.
Is ther any trick to activate CORS really.
If you call the AllowAnyOrigin then the server will set the Access-Control-Allow-Origin: * . In this article, we were more strict and used the WithOrigin method allowing only our client app to access the server’s resources.
Sorry for my unclear remark.
if your ServerWebAPI and your client Programm runs on one Machine. All is OK. But when I install your ServerWebAPI to my old Computer and run
your client-Programnm. The Client Programm run in a timeout because it can’t get to the API.
With Postman I get contact to every installation.
But when I see at the Header Information, I missed the Headertopic
ccess-Control-Allow-Origin: * so I wonder if the CORS works properly.
I made no change in your code. I want to see a solution to adopt it to my WEB-API.
There is also an additional Remark:
if I consume in your client Programm the API:
https://localhost:5001
It Works (WebAPI runs with this Adress) on the same computer, but when I call it with
https://<Servername>:5001 or https://<IP-Adress>:5001
it will not work.
With Postmann all the 3 adresses works properly but again missed the Headertopic
Access-Control-Allow-Origin: *.
In a Sample Programm I consume a Rest-API on GitHub
https://api.github.com/repos/dotnet/AspNetCore.Docs/branches
And when I call this adress in Postmann I get Access-Control-Allow-Origin: *.
in the Header of the response.
Hope this is a Problem of my or configuration.
Thanks for your quick response.
Well, as I replied to you, our project (the server one) currently configures the CORS policy by using a strict rule with the WithOrigin method. This means that only the client from the same origin as in the WithOrigin method can access the server app. If you send a request to the API with that configuration you get the response as in the image on the left side.
But, if you change the CORS configuration, from WithOrigin to AllowAnyOrigin, you should get the required Access-Control-Allow-Origin value. You can see that in the image on the right side.
So, this is the way to set up CORS properly. I am not sure if this is going to help you, but if your goal is to set Access Control to *, this will do it.
I understand your comment well.
this is what I hve expected.
But When I run your server Project on my Computer I get in PostMan the result without Access-Control-Allow-Origin value as expected. See Picture
But when I use a old WCF CORS based API (see other picture) there is Access-Control-Allow-Origin value set to “*”.
And also when I Used AllowAny and I installed your sample server project to another computer. I get correct reaction with postman but with your client Programm I get no result.
Access-Control-Allow-Origin value after acess with postman is also not set to an Value.
I Used Visaul Studio 16.11.4 (newest) Is ther any action I have to do that the CORS Middleware react as expected?
Thanks for your easy-to-understand example.
Thank you for reading it. All the best.
Hi,
I have just started watching your videos and reading your blogs .
I must say these are very good and very precise .
,I am upgrading myself and yours blog articles are very helpful to me.
I was working as classic Asp developer before covid 19 in a travel company lost my job and searching new one.
Once again thank you for providing such nice article.
Hello Prashant. I’m glad you like the articles and videos. I really hope that our content can have at least some impact on your successful job search. Wish you all the best.
I have a dotnet core 3.0 service running on docker in AKS and can’t get passed CORS. I have an angular app http://localhost:4200 as my origin. When I deploy my service it works for the first couple of try’s but stops working shortly after. Blocked by origin. Any ideas?
app.UseCors(builder => builder.WithOrigins(“http://localhost:4200”).AllowAnyHeader().AllowAnyMethod().AllowCredentials());
It’s really odd as it works for about the first 20 try’s after a deploy to AKS.
Very good post! 🙂
Thank you Leivas Santos. We hope you will enjoy our other articles as well (if you find something interesting for you 😀 ). All the best.
Thanks for this. You’ve made configuring this easy to understand!
Thank you George for reading it. We are doing our best to provide a quality content for our readers.