Implementing CORS in ASP.NET Core Web API: Enabling Secure Cross-Origin Resource Sharing

Implementing CORS in ASP.NET Core Web API: Enabling Secure Cross-Origin Resource Sharing

In the world of web development, ensuring secure communication between different origins is crucial. Cross-Origin Resource Sharing (CORS) comes to the rescue by allowing controlled access to resources on different domains. In this article, we'll delve into the realm of CORS, exploring its purpose and benefits. We'll also guide you through the implementation of CORS in an ASP NET Core Web API, ensuring secure cross-origin interactions.

What is CORS?

CORS is a feature implemented by web browsers to control how web applications on different domains interact with each other.

Imagine you have a website on one domain, and you want to access some resources (like data or images) from another website on a different domain. By default, web browsers are a bit cautious about this because they want to protect you from potential security risks. That's where CORS comes in.

CORS allows web servers to specify who can access their resources and under what conditions. It acts as a communication channel between the web browser and the server, making sure that it's safe for your website to access resources from another domain.

Now, it's important to note that CORS is not a complete security solution on its own. It's more like a helpful tool that works alongside other security measures. CORS focuses on managing cross-origin requests, which are requests made from one domain to another. It helps prevent certain types of attacks that could exploit the same-origin policy, a security feature in browsers.

However, CORS doesn't handle all aspects of security. It doesn't provide end-to-end security for data transfer or protect against all possible threats. For a robust security setup, you need to combine CORS with other practices like proper authentication and authorization, input validation, encryption, and following secure coding practices.

Think of CORS as a friendly bouncer at a party who checks your invitation before allowing you in. It helps control access to resources between different domains, but you still need to make sure you have other security measures in place to throw a safe and secure party.

So, remember, while CORS is a helpful feature, it's not the whole security package. It's part of a bigger picture, and by combining it with other security practices, you can ensure the overall safety of your web application.

Why Should I Use CORS?

CORS is essential when you want to allow clients from different domains to access and interact with resources on your web API. By enabling CORS, you can securely expose selected resources to trusted origins, facilitating seamless data exchange between client applications and your API. CORS helps prevent unauthorized cross-origin requests while promoting collaboration and interoperability among web applications.

Enabling a CORS in your Web App

To add CORS to your web application you have to call the AddCors method in the services container and define a CORS policy. In the code example below a default policy is added and is set to allow any method and any header from the defined source https://www.example.com in this case, this means that your web application will not allow it to be consumed from any other source.

builder.Services.AddCors(options => 
{
    options.AddDefaultPolicy(builder =>
    {
        builder.WithOrigins("<https://www.example.com>")
                .AllowAnyMethod()
                .AllowAnyHeader();
    });
});

Now the last piece of the puzzle is to set your app to use CORS in the middleware pipeline as shown below. An important tip is that you must call UseCors after UseRouting and before the UseAuthorization method to work properly.

app.UseHttpsRedirection();

app.UseCors();

app.UseAuthorization();

app.UseResponseCaching();

app.MapControllers();

app.Run();

Default Policies

A default policy is like the one defined above, you have to define the default policy within the AddCors method and set the origins, methods, and headers allowed. Then this policy will be applied globally to your application as the name says it’s the default policy for your web app.

builder.Services.AddCors(options => 
{
    options.AddDefaultPolicy(builder =>
    {
        builder.WithOrigins("<https://www.example.com>")
                .AllowAnyMethod()
                .AllowAnyHeader();
    });
});

Named Policies

Another way to define a CORS policy is through named policies which will not be applied globally to your web app instead you have to enable it using attributes but we will see that in just a bit. The way to define a named policy is pretty similar to a default policy but here you have to call the AddPolicy method instead of the AddDefaultPolicy.

builder.Services.AddCors(options =>
{
		options.AddPolicy("MyNamedPolicy",policy =>
		    {
		        policy.WithOrigins("<http://example.com>",
		                            "<http://www.contoso.com>");
		        policy.AllowAnyMethod();
		        policy.AllowAnyHeader();
		    });
});

Enable CORS using attributes

If instead of enforcing CORS for all your application you just want it on a set of specific endpoints or a controller then you can achieve this using the attribute [UseCors]. You can use this attribute on top of your controller or action and a default CORS policy will be implemented but the preferred way is to use the attribute and pass the Policy Name property to implement that CORS policy. And this is the way to implement named policies as they are not implemented globally or by default as the default policy.

[HttpGet]
[EnableCors(PolicyName = "MyNamedPolicy")]
public async Task<IActionResult> GetPost([FromQuery] QueryParameters parameters)
{
	...
}

This will implement the named policy MyNamedPolicy in the GetPost action. And you might suspect it but in case you don’t, you can implement different CORS policies for different actions and controllers.

Enable CORS for Subdomains

If you want to enable CORS in all the subdomains from a source you have to call the SetIsOriginAllowedToAllowWildcardSubdomains() method.

Taken from Microsoft Documentation: “SetIsOriginAllowedToAllowWildcardSubdomains: Sets the IsOriginAllowed property of the policy to be a function that allows origins to match a configured wildcard domain when evaluating if the origin is allowed.”

Below is an example of how you would have to implement it.

builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowSubdomainsPolicy",
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                .SetIsOriginAllowedToAllowWildcardSubdomains();
        });
});

Specify methods on your CORS policy

So far we have been using the AllowAnyMethod policy but it won’t make much sense to allow external sources to have access to all the method types all the time. For example, imagine you developed a web API and you want full access to your sources so you use the AllowAnyMethod but for external sources, you want to restrict access to PUT and DELETE methods.

builder.Services.AddCors(options => 
{
    options.AddPolicy("MyDomainsPolicy",policy =>
    {
        policy.WithOrigins("<http://mydomain.com>",
                            "<http://www.mydomain2.com>");
        policy.AllowAnyMethod();
        policy.AllowAnyHeader();
    });

    options.AddPolicy("PublicPolicy",policy =>
    {
        policy.AllowAnyOrigin();
        policy.WithMethods("GET", "POST");
        policy.AllowAnyHeader();
    });
});

Set allowed Request Headers

As with methods you can set what headers your application will accept

options.AddPolicy("PublicPolicy",policy =>
    {
        policy.AllowAnyOrigin();
        policy.WithMethods("GET", "POST", "PUT");
        policy.WithHeaders(HeaderNames.Accept, HeaderNames.CacheControl);
    });

Set Exposed Headers

The browser by default will not expose all response headers. So to make other headers available you should use the WithExposedHeaders method.

options.AddPolicy("PublicPolicy",policy =>
    {
        policy.AllowAnyOrigin()
              .WithMethods("GET", "POST", "PUT")
              .WithHeaders(HeaderNames.Accept, HeaderNames.CacheControl)
              .WithExposedHeaders("x-custom-header");

    });

The only response headers that are available by default are the next:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

Disable CORS

Finally, if you want to disable CORS from a controller or an action you can use the [DisableCors] attribute to achieve it.

[HttpPost]
[DisableCors]
public async Task<IActionResult> CreatePost(AddPostDTO addPostDTO)
{
	...
}

It is important to say that DisableCors will not disable CORS that have been enabled using endpoint routing. An example of CORS enabled with endpoint routing is provided below taken from the Microsoft Documentation.

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/echo",
        context => context.Response.WriteAsync("echo"))
        .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapControllers()
             .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapGet("/echo2",
        context => context.Response.WriteAsync("echo2"));

    endpoints.MapRazorPages();
});

Conclusion

Enabling CORS in your ASP.NET Core Web API is a crucial step to ensure secure cross-origin resource sharing. By implementing CORS, you can control and allow access to resources from trusted origins, promoting collaboration and interoperability. Understanding the benefits and drawbacks of CORS will help you make informed decisions while building secure and scalable web applications. Implement CORS in your ASP.NET Core Web API using the provided steps

Follow me for Developer Tips and Clean Code Tips

From tomorrow I will start sharing advice for software developers from the experiences I’ve lived working in big enterprises as a freelancer, also I will share useful code snippets that can help you write cleaner and more maintainable code for your job or side projects. All this will be on my Twitter page so go and start following me so you don’t miss a single tip @OscarOsempu. We are almost reaching 15k monthly views on Unit Coding, Thanks for being along with me on this exciting journey, to be honest, it was harder than I thought it would be but I’ve learned a lot from this experience and I’m still developing consistency as I can say it’s something I’ve never been good at, but your comments and feedback have been there to make me push forward and always bring you some quality content while enjoying the ride, what can I say? I’m very blessed to have a community like this, thanks for everything!