CORS (Cross-Origin Resource Sharing) in C# and .NET

Image

The Ultimate Guide to Mastering Performance Optimization in React

Moltech Solution Pvt.Ltd.

March 6, 2024
Web Development

CORS, or Cross-Origin Resource Sharing, is a crucial security mechanism that enables web applications to request resources from a domain other than their own.

This blog post will explore the concept of CORS, its importance, and the steps to implement it successfully in C# and .NET frameworks.


    1. What is CORS (Cross-Origin Resource Sharing)?

    CORS (Cross-Origin Resource Sharing) is a browser security feature that regulates resource sharing between different domains to enhance security.

      • Controls access to web server resources by external domains.

      • Prevents unauthorized cross-origin requests by default..

      • Ensures secure data exchange between trusted origins.

      • Configurable on servers to allow specific origins or methods.


    Scenario Overview

    If you have a web application hosted onhttp://frontend-app.comthat needs to fetch data from a REST API hosted onhttp://api.backend-server.com, then you’re dealing with different origins (different domains). The browser enforces the Same-Origin Policy, which blocks requests made from one origin to another unless explicitly allowed by the server.

    Let’s see an example of a shopping website fetching product data.

    You visit https://mystore.com, an online shopping website. The website’s frontend needs to fetch product details, pricing, and inventory from its backend API hosted athttps://api.mystore.com. Since the frontend and backend are on different origins (different subdomains), the browser enforces the Same-Origin Policy and blocks the request unless CORS is properly configured.


    1. The Frontend Makes a Request

    When you browse the product page on https://mystore.com, the frontend sends a request to the backend API to fetch product data:


    1 2 3 4 5 6 7 8 9 fetch('https://api.mystore.com/products',{ method: 'GET', headers: { 'Content-Type': 'application/json', }, }) . then( (response) => response.json() ) . then( (data) => console.log(data) ) . catch( (error) => console.error('CORS Error:', error) );

    2. Same-Origin Policy Check

    The browser checks the origin of the request:


      • Frontend Origin:https://mystore.com
      • Backend API Origin:https://api.mystore.com

    Since these origins are different, the browser enforces the Same-Origin Policy and blocks the request, returning a CORS error like this:


    1 Access to fetch at 'https://api.mystore.com/products' from origin 'https://mystore.com' has been blocked by CORS policy.

    How to Enable CORS in .NET 8.0

    The scenario described can be addressed using the following example in .NET Core 8.0.


    1. Add CORS Middleware

    Make sure the Microsoft.AspNetCore. Cors package is installed (usually included by default in ASP.NET Core)


    1 dotnet add package Microsoft.AspNetCore.Cors

    Step 1: Add CORS Services

    Add the CORS policy to allow requests from your frontend origin.

    You can configure CORS by calling AddCors in the builder.Services section of your Program.cs file:


    1 dotnet add package Microsoft.AspNetCore.Cors
    1 2 3 4 5 6 7 8 9 10 11 12 13 14 var builder = WebApplication.CreateBuilder(args); // Add CORS services builder.Services.AddCors(options => { options.AddPolicy("AllowSpecificOrigin", policy => { policy.WithOrigins("https://mystore.com") // Allow requests from this origin .AllowAnyHeader() // Allow all headers .AllowAnyMethod(); // Allow all HTTP methods }); }); builder.Services.AddControllers(); // Add API controllers var app = builder.Build();

    "AllowSpecificOrigin" is a policy name that can be used in middleware in the subsequent step.


    Step 2: Apply the CORS Policy

    Use the CORS middleware to apply the policy. This configuration can be applied globally across the entire application or tailored to specific endpoints.

    Global Application

    1 2 3 4 5 6 7 8 var app = builder.Build(); // Use the CORS policy globally app.UseCors("AllowSpecificOrigin"); app.MapControllers(); app.Run();

    Per Endpoint

    We can also Apply the CORS policy to specific controllers or endpoints:


    1 app.MapControllers().RequireCors("AllowSpecificOrigin");

    Step 3: Test the CORS Configuration

    You can test the CORS headers using browser developer tools. Make sure the
    Access-Control-Allow-Origin header is present in the server's response.

      • Ensure your API server includes the appropriate CORS headers in its responses when requests come from allowed origins like (https://mystore.com in this example).

      • Use your browser's developer tools (typically the Network tab) to inspect the API response and confirm that it includes the necessary headers such as

        1 Access-Control-Allow-Origin: https://mystore.com


Access-Control-Allow-Origin: https://mystore.com

Key CORS Headers

Cross-Origin Resource Sharing (CORS) utilizes certain HTTP headers to decide if a browser should permit requests from different origins. These headers are crucial for securing cross-origin requests and adhering to the rules set by the server.


1. Access-Control-Allow-Origin: Defines the origins that are allowed to access the resource.


1 Access-Control-Allow-Origin: https://example.com
      • A missing or incorrect header results in the browser blocking the request.

      • Restricts access to trusted origins, preventing unauthorized cross-origin requests.

2. Access-Control-Allow-Methods : The HTTP methods permitted for cross-origin requests include GET, POST, and HEAD.


1 Access-Control-Allow-Methods: GET, POST, PUT
      • Make sure that the client is only able to carry out actions that are allowed on the resource.

3. Access-Control-Allow-Headers : Defines the HTTP headers that may be utilized within a request.


1 Access-Control-Allow-Headers: Content-Type, Authorization
      • This enables the client to incorporate custom or restricted headers such as Authorization or X-Custom-Header.

4 . Access-Control-Expose-Headers : Response header specifies which headers are accessible to the browser in the response to a cross-origin request.

      • By default, browsers limit access to sensitive headers such as ‘Content-Length’ or custom headers.

      • This header allows clients to read specified headers, enhancing data access control.

5. Access-Control-Max-Age : The duration for which a browser can cache the results of a preflight request is specified by the `Access-Control-Max-Age` header. This value is set in seconds and dictates how long the preflight response is valid before a new preflight request must be made.


1 Access-Control-Max-Age: 3600
      • Minimizing repetitive preflight requests enhances performance.

6. Access-Control-Allow-Credentials: Indicates whether cookies or credentials can be sent in cross-origin requests.


1 Access-Control-Allow-Credentials: true

The Access-Control-Allow-Credentials header is crucial for enabling credentials like cookies, authorization headers, or TLS client certificates in cross-origin requests. It needs to be set to ‘true’ to activate this feature.

Tight Coupling with Access-Control-Allow-Origin, Access-Control-Allow-Credentials: true cannot be used with a wildcard (*) in the Access-Control-Allow-Origin header.

If misconfigured, credentials might be exposed to unauthorized origins, leading to security vulnerabilities like session hijacking or cross-site scripting (XSS).

Benefits of Using CORS

CORS is used to overcome the same-origin policy enforced by web browsers, which restricts web pages from making requests to a domain other than the one that served the web page. Here’s why CORS is essential:

    • API Security:

    • Browsers enforce the Same-Origin Policy to prevent websites from accessing resources on another domain unless explicitly allowed.

    • This protects users from malicious websites trying to access sensitive data from other origins (like cookies or APIs) (cross-origin attacks)

  1. Cross-Origin Requests in Modern Applications

    • Many web applications involve cross-origin communication

    • A frontend hosted athttps://example.comfetching data from an API athttps://api.example.com.

    • Third-party integrations like Google Maps, payment gateways, or external APIs.

    • Without CORS, such requests would be blocked by the browser.

  2. Controlled Resource Sharing OR Flexibility

    CORS allows fine-grained control over shared resources:

    • Define allowed origins, methods, and headers.

    • Specify how long responses can be cached (Access-Control-Max-Age).

    • Enable specific use cases like credentials sharing (Access-Control-Allow-Credentials).

  3. Compliance with Browser Security Policies:

    • Browsers enforce same-origin policies for a reason, and CORS helps maintain security while allowing legitimate cross-origin communication.

When Should We Use CORS?

CORS should be enabled in scenarios such as:


  1. Frontend-Backend Hosted on Different Domains or SPA

    • For APIs or services hosted externally, such as payment gateways or cloud APIs.

  2. Third-Party Integrations:

    • For APIs or services hosted externally, such as payment gateways or cloud APIs.

  3. Cross-Origin API Calls

    • To fetch or send data to a server on a different domain.

  4. Microservices Architecture:

    • A microservices-based system where services are deployed on different subdomains or hosts (e.g., auth.example.com, data.example.com).

There are many examples with different architectures.

Best Practices for Configuring CORS

  1. Avoid Using Access-Control-Allow-Origin: *

    • Allowing all origins (*) is insecure, especially for APIs that deal with sensitive data or require authentication.

    • Dynamically set the Access-Control-Allow-Origin header based on the Origin header in the request.

  2. Use Whitelisted Domains

    • Restricting CORS to known, trusted domains minimizes the risk of unauthorized access.

  3. Enable Access-Control-Allow-Credentials Only When NecessaryAccess-Control-Allow-Origin: *

    • Allowing credentials (e.g., cookies, authorization headers) introduces security risks if origins are not tightly controlled.

    • Combine it with an explicit Access-Control-Allow-Origin (wildcards are not allowed with credentials).

  4. Leverage Access-Control-Max-Agefor Preflight Caching

    • Preflight requests (OPTIONS) can introduce latency to API calls. Utilize the Access-Control-Max-Age header to cache preflight responses for an appropriate period, such as 3600 seconds, which is equivalent to one hour.

  5. . Match theOriginHeader Dynamically

    • A static Access-Control-Allow-Origin header may be unsuitable for APIs that serve multiple domains. Instead, dynamically match the incoming Origin header and set it accordingly in the response.


  6. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 app.Use(async (context, next) => { var allowedOrigins = new[] { "https://example.com", "https://api.example.com" }; var origin = context.Request.Headers["Origin"].ToString(); if (!string.IsNullOrEmpty(origin) && allowedOrigins.Contains(origin)) { context.Response.Headers.Add("Access-Control-Allow-Origin", origin); context.Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); context.Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization"); context.Response.Headers.Add("Access-Control-Allow-Credentials", "true"); } if (context.Request.Method == "OPTIONS") { context.Response.StatusCode = 204; // No Content return; } await next.Invoke(); });

    You can configure “AllowedOrigins” within the appsettings.json file. as below example.


    1 2 3 4 5 6 7 8 9 10 11 { "CorsSettings": { "AllowedOrigins": [ "https://example.com", "https://api.example.com" ], "AllowedMethods": "GET, POST, PUT, DELETE, OPTIONS", "AllowedHeaders": "Content-Type, Authorization", "AllowCredentials": true } }

    Add a section in your appsettings.json to define the allowed origins and other CORS settings. Now, load the CORS settings from appsettings.json in Program.cs


    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var builder = WebApplication.CreateBuilder(args); // Load CORS settings from configuration var corsSettings = builder.Configuration.GetSection("CorsSettings"); var allowedOrigins = corsSettings.GetSection("AllowedOrigins").Get<string[ ]>(); // Register CORS builder.Services.AddCors(options => { options.AddPolicy("CorsPolicy", builder => { builder.WithOrigins(allowedOrigins) .WithMethods(corsSettings["AllowedMethods"].Split(", ")) .WithHeaders(corsSettings["AllowedHeaders"].Split(", ")) .AllowCredentials(); }); });

    The approach mentioned offers benefits such as centralized configuration and environment-specific configurations.

  7. . Protect Against CSRF (Cross-Site Request Forgery)

    • CORS does not prevent CSRF attacks, particularly when credentials are involved. Employ CSRF tokens and validate requests server-side. Make sure cookies are marked with SameSite=strict or SameSite=lax.

  8. . Provide Clear Error Messages for Blocked Requests

    • Debugging CORS issues can pose a challenge for developers. It’s beneficial to log CORS errors on the server and offer meaningful messages to clients when their requests are blocked

Conclusion

CORS plays a crucial role in today’s web development, facilitating secure interactions across various domains, which is particularly vital for APIs and handling cross-origin requests. By incorporating CORS into .NET, developers can guarantee smooth communication for Single Page Applications, external integrations, and backend services.