Back to: ASP.NET Core Web API Tutorials
CORS in ASP.NET Core Web API
In this article, I will discuss CORS (Cross-Origin Resource Sharing) in ASP.NET Core Web API Applications. In modern web development, applications often interact with APIs hosted on different domains. However, browsers prevent unauthorized cross-origin interactions. This is where CORS (Cross-Origin Resource Sharing) comes into play.
What is the Same-Origin Policy (SOP)?
The Same-Origin Policy (SOP) is a Security Feature implemented by Web Browsers to restrict a web page from making requests (JavaScript or AJAX calls) to a different origin (domain, protocol, or port) than the one that served the page. This policy ensures that a web page can only make requests to another URL if the origin (combination of Protocol, Domain, and Port) of the request matches the origin of the web page.
Specifically, the origin is defined by the protocol (HTTP/HTTPS), Domain (example.com), and port (80, 443). If any of these differ between a web page and a requested resource, the request is considered cross-origin and is subject to restrictions.
Default Behaviour of Browsers in AJAX Requests?
When a web page attempts to make an AJAX request to a different origin, browsers enforce SOP to determine whether the request should be allowed. Let’s understand the scenarios with examples. For a better understanding, please have a look at the following diagram.
Let us understand the above scenarios:
Same-Origin Request:
- Client: https://example.com/client
- Server: https://example.com/api
- Outcome: The AJAX request is successful because both the client and server share the same origin (same domain, protocol, and port). CORS is not required here because the request is within the same origin.
Cross-Origin Request Without CORS Enabled:
- Client: https://myclient.com
- Server: https://example.com/api
- Outcome: The AJAX request fails because the request is coming from a different domain (myclient.com), and the server has not enabled CORS for this domain. The browser enforces the Same-Origin Policy and blocks the request unless CORS headers are properly configured by the server.
Cross-Origin Request With CORS Enabled:
- Client: https://myclient.com
- Server: https://example.com/api
- Outcome: The AJAX request is successful because the server has CORS enabled specifically for the domain https://myclient.com. This allows the server to accept cross-origin requests from this particular domain.
What is CORS?
CORS (Cross-Origin Resource Sharing) is a mechanism that allows servers to specify which origins are allowed to access their resources. It enables a web application running at one origin (domain) to make requests for resources on a different origin. CORS headers are added to server responses to tell the browser whether to allow or deny cross-origin requests. That means it is a way to bypass the Same-Origin Policy (SOP) by allowing servers to specify who can access their resources.
CORS is configured on the server side by adding specific HTTP headers (e.g., Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers) to the response. These headers instruct the browser on how to handle the request based on the origins, methods, and headers allowed.
How Does CORS Work?
When a client application sends an AJAX request to Web API hosted on a different origin, the process involves several steps that ensure the request is safe and adheres to the server’s CORS policy. For a better understanding, please have a look at the below image:
Let us understand how CORS works step by step:
First Request (Initial Preflight Request)
Browser Sends a Preflight Request:
When the browser detects that a cross-origin request is being made (e.g., from http://example.com to http://api.example.com), it sends a preflight request first. This preflight request is an HTTP OPTIONS request sent to the server, which includes information about the intended HTTP method and headers of the actual request. This preflight request checks whether the server permits the actual request based on the method (e.g., GET, POST) and headers (e.g., custom headers).
Server Responds to the Preflight Request:
The server evaluates the preflight request and decides whether the actual request is allowed based on its CORS policy. If the server allows the request, it sends a preflight response with appropriate headers (e.g., Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers). The following are the Preflight Response Headers:
- Access-Control-Allow-Origin: Specifies which origins are allowed.
- Access-Control-Allow-Methods: Specifies which HTTP methods are allowed.
- Access-Control-Allow-Headers: Specifies which request headers are allowed.
- Access-Control-Max-Age: Specifies how long the preflight result can be cached.
Browser Sends the Actual Request:
After receiving a positive response to the preflight request, the browser caches the preflight result and sends the actual request to the server. The server processes the actual request and returns the desired response (e.g., JSON data).
Preflight Result Caching:
The browser caches the result of the preflight response for a certain duration (controlled by the Access-Control-Max-Age header sent by the server).
Second Request (Cached Preflight Result)
- Browser Checks Preflight Result Cache: For subsequent requests to the same server with the same method and headers, the browser first checks its preflight result cache. If the preflight result is still valid (i.e., not expired), the browser skips sending another preflight request.
- Browser Sends the Actual Request Directly: Since the preflight result is already cached, the browser directly sends the actual request to the server. The server processes the request and sends back the response.
Example to Understand CORS Issues in ASP.NET Core Web API:
To illustrate how CORS operates and how to resolve CORS-related issues, we will set up a practical scenario involving two ASP.NET Core applications:
- ASP.NET Core Web API Application (Server): Exposes endpoints for CRUD operations on a User Entity.
- ASP.NET Core MVC Application (Client): Attempts to perform AJAX-based CRUD operations against the Web API.
So, we will create a single-page application to perform the CRUD Operation using Restful web services. We will develop the following client applications:
Creating the ASP.NET Core Web API Application (Web Service)
This ASP.NET Core Web API application will expose endpoints for performing CRUD operations on a User Entity. It will be configured to restrict cross-origin requests, which will cause CORS issues when accessed from another application using AJAX. So, first create a new ASP.NET Core Web API Project named UserServiceAPI.
Once you create the project, please install the Entity Framework Core packages by executing the following commands in the Package Manager Console:
- Install-Package Microsoft.EntityFrameworkCore.SqlServer
- Install-Package Microsoft.EntityFrameworkCore.Tools
Create the User Model
First, create a folder called Models within the project root directory. In the Models folder, create a new class file named User.cs and copy and paste the following code. This will be the User Entity on which we will perform the CRUD operations.
namespace UserServiceAPI.Models { public class User { public int Id { get; set; } public string FullName { get; set; } public string Email { get; set; } public string Position { get; set; } } }
Create the UserDbContext Class
Create a class file named UserDbContext.cs in the Models folder, then copy and paste the following code.
using Microsoft.EntityFrameworkCore; namespace UserServiceAPI.Models { public class UserDbContext : DbContext { public UserDbContext(DbContextOptions<UserDbContext> options) : base(options) { } public DbSet<User> Users { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // Seeding dummy data modelBuilder.Entity<User>().HasData( new User { Id = 1, FullName = "John Doe", Email = "john.doe@example.com", Position = "Developer" }, new User { Id = 2, FullName = "Jane Smith", Email = "jane.smith@example.com", Position = "Manager" }, new User { Id = 3, FullName = "Sara Taylor", Email = "sara.taylor@example.com", Position = "Manager" }, new User { Id = 4, FullName = "Pam Jordon", Email = "pam.jordon@example.com", Position = "Developer" } ); } } }
Create the UserRepository
In the Models folder, create another class file named UserRepository.cs and copy and paste the following code. The repository interacts with the database using EF Core to perform the CRUD Operations using the User entity.
using Microsoft.EntityFrameworkCore; namespace UserServiceAPI.Models { public class UserRepository { private readonly UserDbContext _context; public UserRepository(UserDbContext context) { _context = context; } public async Task<IEnumerable<User>> GetAllUsersAsync() { return await _context.Users.ToListAsync(); } public async Task<User?> GetUserByIdAsync(int id) { return await _context.Users.FindAsync(id); } public async Task AddUserAsync(User user) { await _context.Users.AddAsync(user); await _context.SaveChangesAsync(); } public async Task UpdateUserAsync(User user) { _context.Users.Update(user); await _context.SaveChangesAsync(); } public async Task DeleteUserAsync(int id) { var user = await _context.Users.FindAsync(id); if (user != null) { _context.Users.Remove(user); await _context.SaveChangesAsync(); } } } }
Create the User Controller
In the Controllers folder, create a new API Empty controller named UserController and copy and paste the following. The following controller exposes the endpoints to be consumed by the client applications to perform the CRUD Operations on the User entity.
using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; using UserServiceAPI.Models; namespace UserServiceAPI.Controllers { [Route("api/[controller]/[action]")] [ApiController] public class UserController : ControllerBase { private readonly UserRepository _userRepository; public UserController(UserRepository userRepository) { _userRepository = userRepository; } [HttpGet] public async Task<ActionResult<List<User>>> GetAllUsers() { return Ok(await _userRepository.GetAllUsersAsync()); } [HttpGet("{id}")] public async Task<ActionResult<User>> GetUserById(int id) { var user = await _userRepository.GetUserByIdAsync(id); if (user == null) { return NotFound(); } return Ok(user); } [HttpPost] public async Task<ActionResult> AddUser(User user) { await _userRepository.AddUserAsync(user); return CreatedAtAction(nameof(GetUserById), new { id = user.Id }, user); } [HttpPut("{id}")] public async Task<ActionResult> UpdateUser(int id, User user) { if (id != user.Id) { return BadRequest(); } await _userRepository.UpdateUserAsync(user); return NoContent(); } [HttpDelete("{id}")] public async Task<ActionResult> DeleteUser(int id) { await _userRepository.DeleteUserAsync(id); return NoContent(); } } }
Modify appsettings.json
Make sure to add a connection string to your appsettings.json file. So, please modify the appsettings.json file as follows.
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "EFCoreDBConnection": "Server=LAPTOP-6P5NK25R\\SQLSERVER2022DEV;Database=UsersDB;Trusted_Connection=True;TrustServerCertificate=True;" } }
Modify the Program Class:
Register the UserDbContext and UserRepository in the dependency injection container. Please modify the Program class as follows:
using Microsoft.EntityFrameworkCore; using UserServiceAPI.Models; namespace UserServiceAPI { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers() .AddJsonOptions(options => { // This will use the property names as defined in the C# model options.JsonSerializerOptions.PropertyNamingPolicy = null; }); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); //Configure the ConnectionString and DbContext class builder.Services.AddDbContext<UserDbContext>(options => { options.UseSqlServer(builder.Configuration.GetConnectionString("EFCoreDBConnection")); }); // Register UserRepository builder.Services.AddScoped<UserRepository>(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.Run(); } } }
Database Migration
Next, we need to generate the Migration and update the database schema. So, open the Package Manager Console and Execute the Add-Migration and Update-Database commands as follows.
With this. our Database with Users tables should be created as shown in the image below:
Test the API
At this stage, you can test the API using tools like Postman, Fiddler, or Swagger to ensure that the CRUD operations work as expected. We have not yet done the CORS configuration, so cross-origin requests will be blocked. Please note the port number where your ASP.NET Core Web API project runs. In my case, the application is running on the 7082 port number.
Creating the ASP.NET Core MVC Application (Client Application)
This ASP.NET Core MVC application will attempt to perform CRUD operations on the User entity via AJAX requests to the Web API, resulting in CORS issues since the Web API does not allow cross-origin requests. So, create a new ASP.NET Core Project using the Model View Controller template and give the project name as UserClientApp.
Create the User Model
In the Models folder, create a new class file named User.cs, and copy and paste the following code.
namespace UserClientApp.Models { public class User { public int Id { get; set; } public string FullName { get; set; } public string Email { get; set; } public string Position { get; set; } } }
Modify Home Controller
By default, the project is created with HomeController. So, please modify the HomeController as follows:
using Microsoft.AspNetCore.Mvc; using UserClientApp.Models; using System.Text.Json; namespace UserClientApp.Controllers { public class HomeController : Controller { //This action method will load the list of users from the API //and pass it to the view on the initial load. public async Task<IActionResult> Index() { //Create an Instance of HttpClient HttpClient _httpClient = new HttpClient(); //Set the base Address //Please replace the port on which your Web API Application is running _httpClient.BaseAddress = new Uri("https://localhost:7274/"); //When the Page Load we want to display the List of User //Specify the endpoint which returns the list of Users //Here, we will not get any CORS issue, this is because of Server to Server call //That means the MVC Application Server will communicate with the Web API Server var response = await _httpClient.GetStringAsync("api/User/GetAllUsers"); //Convert the Response which is in JSON format to List<User> var users = JsonSerializer.Deserialize<List<User>>(response); //Pass the List of Users to the View return View(users); } } }
Modify the Index View
In the Views/Home folder, update the Index.cshtml view to include forms for creating, updating, and deleting users:
@model List<User> @{ ViewData["Title"] = "User Management"; } <h2 class="text-center my-4">👥 User Management</h2> <div class="container"> <div class="row"> <div class="col-md-12"> <!-- Success and Error Messages --> <div id="successMessage" class="alert alert-success alert-dismissible fade show" role="alert" style="display:none;"> <span id="successText"></span> 😊 <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> </div> <div id="errorMessage" class="alert alert-danger alert-dismissible fade show" role="alert" style="display:none;"> <span id="errorText"></span> 😟 <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> </div> <button id="loadUsers" class="btn btn-primary mb-3"><i class="bi bi-arrow-repeat"></i> 🔄 Refresh User List</button> <button id="createUserBtn" class="btn btn-success mb-3"><i class="bi bi-person-plus"></i> ➕ Add New User</button> <div id="createUserForm" class="mb-4 card card-body shadow-sm" style="display: none;"> <h4 class="card-title">🆕 Add New User</h4> <div class="form-group mb-3"> <label for="createFullName">Full Name</label> <input type="text" id="createFullName" class="form-control" placeholder="Enter full name" /> </div> <div class="form-group mb-3"> <label for="createEmail">Email</label> <input type="email" id="createEmail" class="form-control" placeholder="Enter email" /> </div> <div class="form-group mb-3"> <label for="createPosition">Position</label> <input type="text" id="createPosition" class="form-control" placeholder="Enter position" /> </div> <button id="createUser" class="btn btn-success mt-2"><i class="bi bi-save"></i> 💾 Save User</button> <button id="cancelCreate" class="btn btn-secondary mt-2"><i class="bi bi-x-circle"></i> ❌ Cancel</button> </div> <table class="table table-hover table-striped shadow-sm"> <thead class="table-dark"> <tr> <th>🆔 ID</th> <th>📛 Full Name</th> <th>📧 Email</th> <th>💼 Position</th> <th>🔧 Actions</th> </tr> </thead> <tbody id="userTableBody"> @foreach (var user in Model) { <tr> <td>@user.Id</td> <td>@user.FullName</td> <td>@user.Email</td> <td>@user.Position</td> <td> <button class="btn btn-info editUser" data-id="@user.Id"><i class="bi bi-pencil"></i> ✏️ Edit</button> <button class="btn btn-danger deleteUser" data-id="@user.Id"><i class="bi bi-trash"></i> 🗑️ Delete</button> </td> </tr> } </tbody> </table> </div> </div> </div> <!-- Bootstrap Modal for Editing User --> <div class="modal fade" id="editUserModal" tabindex="-1" aria-labelledby="editUserModalLabel" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="editUserModalLabel">✏️ Edit User Details</h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> <input type="hidden" id="editUserId" /> <div class="form-group mb-3"> <label for="editFullName">Full Name</label> <input type="text" id="editFullName" class="form-control" placeholder="Enter full name" /> </div> <div class="form-group mb-3"> <label for="editEmail">Email</label> <input type="email" id="editEmail" class="form-control" placeholder="Enter email" /> </div> <div class="form-group mb-3"> <label for="editPosition">Position</label> <input type="text" id="editPosition" class="form-control" placeholder="Enter position" /> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><i class="bi bi-x-circle"></i> ❌ Cancel</button> <button type="button" class="btn btn-primary" id="updateUserBtn"><i class="bi bi-save"></i> 💾 Update User</button> </div> </div> </div> </div> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.min.js"></script> <script> $(document).ready(function () { // Show Create User Form $("#createUserBtn").click(function () { $("#createUserForm").show(); }); // Cancel Create User $("#cancelCreate").click(function () { $("#createUserForm").hide(); }); // Load Users $("#loadUsers").click(function () { loadUsers(); }); // Function to Load Users function loadUsers() { $.ajax({ url: "https://localhost:7082/api/user/getallusers", type: "GET", success: function (data) { var rows = ""; data.forEach(function (user) { rows += "<tr><td>" + user.Id + "</td><td>" + user.FullName + "</td><td>" + user.Email + "</td><td>" + user.Position + "</td><td><button class='btn btn-info editUser' data-id='" + user.Id + "'><i class='bi bi-pencil'></i> ✏️ Edit</button><button class='btn btn-danger deleteUser' data-id='" + user.Id + "'><i class='bi bi-trash'></i> 🗑️ Delete</button></td></tr>"; }); $("#userTableBody").html(rows); }, error: function (xhr, status, error) { showErrorMessage("⚠️ Oops! An error occurred while loading the user list. Please try again."); } }); } // Create User $("#createUser").click(function () { var user = { FullName: $("#createFullName").val(), Email: $("#createEmail").val(), Position: $("#createPosition").val() }; $.ajax({ url: "https://localhost:7082/api/user/adduser", type: "POST", contentType: "application/json", data: JSON.stringify(user), success: function () { showSuccessMessage("🎉 New user created successfully!"); loadUsers(); // Reload the list of users $("#createUserForm").hide(); }, error: function (xhr, status, error) { showErrorMessage("⚠️ Oops! An error occurred while creating the user. Please try again."); } }); }); // Edit User $(document).on("click", ".editUser", function () { var userId = $(this).data("id"); $.ajax({ url: "https://localhost:7082/api/user/getuserbyid/" + userId, type: "GET", success: function (user) { $("#editUserId").val(user.Id); $("#editFullName").val(user.FullName); $("#editEmail").val(user.Email); $("#editPosition").val(user.Position); $("#editUserModal").modal("show"); }, error: function (xhr, status, error) { showErrorMessage("⚠️ Oops! An error occurred while loading the user details. Please try again."); } }); }); // Update User $("#updateUserBtn").click(function () { var user = { Id: $("#editUserId").val(), FullName: $("#editFullName").val(), Email: $("#editEmail").val(), Position: $("#editPosition").val() }; $.ajax({ url: "https://localhost:7082/api/user/updateuser/" + user.Id, type: "PUT", contentType: "application/json", data: JSON.stringify(user), success: function () { showSuccessMessage("🎉 User details updated successfully!"); $("#editUserModal").modal("hide"); loadUsers(); // Reload to fetch the updated list of users }, error: function (xhr, status, error) { showErrorMessage("⚠️ Oops! An error occurred while updating the user. Please try again."); } }); }); // Delete User $(document).on("click", ".deleteUser", function () { var userId = $(this).data("id"); if (confirm("❗ Are you sure you want to delete this user? This action cannot be undone.")) { $.ajax({ url: "https://localhost:7082/api/user/deleteuser/" + userId, type: "DELETE", success: function () { showSuccessMessage("🗑️ User deleted successfully."); loadUsers(); // Reload to fetch the updated list of users }, error: function (xhr, status, error) { showErrorMessage("⚠️ Oops! An error occurred while deleting the user. Please try again."); } }); } }); // Function to show success message function showSuccessMessage(message) { $("#successText").text(message); $("#successMessage").show(); setTimeout(function () { $("#successMessage").fadeOut("slow"); }, 8000); } // Function to show error message function showErrorMessage(message) { $("#errorText").text(message); $("#errorMessage").show(); setTimeout(function () { $("#errorMessage").fadeOut("slow"); }, 8000); } }); </script>
Explanation:
- Create User Form: A form for creating new users is displayed when the “Add New User” button is clicked.
- Edit User: The modal for editing a user is populated with the user’s data when the “Edit” button is clicked.
- Update User: The update button sends a PUT request to the Web API to update the user’s details.
- Delete User: The delete button sends a DELETE request to remove a user from the database.
Run Both Applications
- Run the Web API (UserServiceAPI): The URL is: https://localhost:7082/.
- Run the MVC Application (UserClientApp): The URL is: https://localhost:7196/.
Performing the Create, Update, and Delete Operations
When we try to create, update, or delete users using the ASP.NET Core MVC application, the browser will attempt to send AJAX requests to the ASP.NET Core Web API endpoints. Since the Web API is configured to block cross-origin requests (CORS), we will encounter CORS issues, and the requests will fail.
To understand this better, open the browser developer tool, open the Console tab, and then try to perform the Create, Update, and Delete operation by clicking the respective button. Then you will see the CORS issue as shown in the below image:
Understanding the CORS Issue
In our example, the client application, i.e., ASP.NET Core MVC Application, is hosted at https://localhost:7196/, and the ASP.NET Core Web API application is hosted at https://localhost:7082/. We are making AJAX requests from the MVC Client Application to the Web API Server Application, but the Web API application or Server is not configured to allow cross-origin requests. Let us proceed and see how the browser and server treat this scenario. Let us take the Create User Post Request.
Step 1: Preflight Request
When we try to send a POST request to the server with a header (e.g., Content-Type: application/json), the browser first sends a preflight request (an HTTP OPTIONS Request) to the Web API server to check if the server allows this cross-origin request. The Preflight Request (OPTIONS) contains the following:
- OPTIONS /api/user/adduser HTTP/1.1
- Host: localhost:7082
- Origin: https://localhost:7082
- Access-Control-Request-Method: POST
- Access-Control-Request-Headers: Content-Type
Explanation:
- Host: It specifies the Web Server domain.
- Origin: Specifies the origin of the request (https://localhost:7082). This is the client domain.
- Access-Control-Request-Method: Indicates that the actual request will use the POST method.
- Access-Control-Request-Headers: Lists any headers used in the actual request (e.g., Content-Type).
Step 2: Preflight Response from Server
However, since the ASP.NET Core Web API server is not configured to allow cross-origin requests, the web server will not include the necessary CORS headers in its response. Since the Web API server did not respond with the required CORS headers, the browser blocks the actual POST request, and the AJAX call fails. And you will see an error similar to the following in the browser’s console:
Access to XMLHttpRequest at ‘https://localhost:7082/api/user/adduser’ from origin ‘https://localhost:7196’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
How Can We Overcome the CORS Issue in Our Application?
To overcome the CORS (Cross-Origin Resource Sharing) problem, we need to configure the Web API server to allow CROSS-ORIGIN REQUESTS from the client application. This involves setting up CORS policies in the ASP.NET Core Web API application to specify which origins (Domains, Methods, and Headers) can access the server’s resources.
We can configure the CORS policies at different levels, such as Globally, Controller Level, and the Action Method Level. Let us first understand how to configure Globally, then I will show how to configure at Controller and Action method Level.
Applying CORS Policy Globally:
We need to configure the CORS Policy in the Program.cs file of our ASP.NET Core Web API application. So, please modify the Program.cs class file as follows. This is the most common and straightforward method. We need to define a CORS policy that apply to all controllers and actions.
using Microsoft.EntityFrameworkCore; using UserServiceAPI.Models; namespace UserServiceAPI { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers() .AddJsonOptions(options => { // This will use the property names as defined in the C# model options.JsonSerializerOptions.PropertyNamingPolicy = null; }); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); //Configure the ConnectionString and DbContext class builder.Services.AddDbContext<UserDbContext>(options => { options.UseSqlServer(builder.Configuration.GetConnectionString("EFCoreDBConnection")); }); // Register UserRepository builder.Services.AddScoped<UserRepository>(); // Configure CORS policy to allow requests from the client application builder.Services.AddCors(options => { options.AddPolicy("AllowAllOrigin", builder => { builder.AllowAnyOrigin() // Allow any Origins .AllowAnyHeader() // Allow any headers (like Content-Type) .AllowAnyMethod(); // Allow any HTTP methods (GET, POST, etc.) }); }); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); //Apply CORS Globally app.UseCors("AllowAllOrigin"); app.UseAuthorization(); app.MapControllers(); app.Run(); } } }
Understanding CORS:
The following code in our Program class enables CORS for All Domains, including all methods and all headers.
The UseCors(“AllowAllOrigin”) middleware ensures that the specified CORS policy (specify with the policy name AllowAllOrigin within the AddCors service) is applied to all incoming requests globally. This method is simple and effective when you want to implement the same CORS policy throughout all actions and controllers and allow all clients to access the services using any HTTP methods and headers.
Rebuild and Run Web API
After making these changes, rebuild and run the ASP.NET Core Web API application. Now, the API will allow cross-origin requests from https://localhost:7196 /.
Test Client Application
First, run the server application, then your client application and test the AJAX requests. This time, the requests should succeed without CORS errors, as the Web API server is now configured to accept requests from your client application’s origin.
What Happens When CORS Configured Properly?
Now, let’s see what happens when the server at https://localhost:7082/ is properly configured to allow cross-origin requests from https://localhost:7196/.
Step 1: Preflight Request
When the client application (https://localhost:7196/) tries to send a POST request to the Web API (https://localhost:7082/) server to create a new user, the browser first sends a preflight request to the server to check if the actual request is allowed.
Preflight Request (OPTIONS):
- OPTIONS /api/user/adduser HTTP/1.1
- Host: localhost:7082
- Origin: https://localhost:7196
- Access-Control-Request-Method: POST
- Access-Control-Request-Headers: Content-Type
Here,
- Host: The web server to whom we need to send the request.
- Origin: Specifies the origin of the request (https://localhost:7196). That is the client application origin.
- Access-Control-Request-Method: Indicates that the actual request will use the POST method.
- Access-Control-Request-Headers: Lists any specific headers used in the actual request, such as Content-Type.
Step 2: Preflight Response from Server
As the Web API server at https://localhost:7082/ is configured to allow requests from https://localhost:7196/, it responds to the preflight request with the following CORS headers:
- Access-Control-Allow-Origin: https://localhost:7196
- Access-Control-Allow-Methods: POST, GET, OPTIONS
- Access-Control-Allow-Headers: Content-Type
Here,
- Access-Control-Allow-Origin: The server allows requests from https://localhost:7196.
- Access-Control-Allow-Methods: The server allows POST, GET, and OPTIONS methods.
- Access-Control-Allow-Headers: The server allows the Content-Type header in the request.
Step 3: Actual AJAX Request
Since the preflight request was successful, the browser sends the actual POST request to create a new user.
Step 4: Server Response
The Web API server processes the POST request, creates the new employee, and responds with the appropriate data.
Note: If you check the Network tab, then you should see two requests: One Request using the OPTIONS method and another Request using the POST method.
How to Configure Specific CORS Policy:
To specify specific origins, headers, and methods in our CORS configuration, we need to modify the AddCors method in our Program.cs class to define these explicitly. For a better understanding, please modify the Program class as follows:
using Microsoft.EntityFrameworkCore; using UserServiceAPI.Models; namespace UserServiceAPI { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers() .AddJsonOptions(options => { // This will use the property names as defined in the C# model options.JsonSerializerOptions.PropertyNamingPolicy = null; }); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); //Configure the ConnectionString and DbContext class builder.Services.AddDbContext<UserDbContext>(options => { options.UseSqlServer(builder.Configuration.GetConnectionString("EFCoreDBConnection")); }); // Register UserRepository builder.Services.AddScoped<UserRepository>(); // Configure CORS policy to allow specific origins, headers, and methods builder.Services.AddCors(options => { options.AddPolicy("AllowSpecificOrigin", policy => { policy.WithOrigins("https://localhost:7196", "https://example.com") // Add client origin .WithHeaders("Content-Type", "Authorization", "Any-Custom-Header", "Accept") // Allow specific headers .WithMethods("GET", "POST", "PUT", "DELETE"); // Allow specific methods }); }); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); //Apply CORS Globally app.UseCors("AllowSpecificOrigin"); app.UseAuthorization(); app.MapControllers(); app.Run(); } } }
With the above changes in place, only the Origins, Headers, and Methods configured in the CORS policy can access the servers.
Different ways to Configure CORS in ASP.NET Core Web API:
Configuring CORS (Cross-Origin Resource Sharing) in ASP.NET Core Web API Applications can be done in various ways depending on the level of control you need. We have already discussed how to configure globally. Now, let us proceed and understand how to configure CORS at the Controller and action method level.
CORS Configuration at Controller-Level in ASP.NET Core Web API
Sometimes, we may want to apply different CORS policies to different controllers. We can do this by applying the CORS policy at the controller level. So, first modify the Program class as follows. Here, we have created two CORS Policy.
using Microsoft.EntityFrameworkCore; using UserServiceAPI.Models; namespace UserServiceAPI { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers() .AddJsonOptions(options => { // This will use the property names as defined in the C# model options.JsonSerializerOptions.PropertyNamingPolicy = null; }); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); //Configure the ConnectionString and DbContext class builder.Services.AddDbContext<UserDbContext>(options => { options.UseSqlServer(builder.Configuration.GetConnectionString("EFCoreDBConnection")); }); // Register UserRepository builder.Services.AddScoped<UserRepository>(); // Configure CORS policy to allow specific origins, headers, and methods builder.Services.AddCors(options => { options.AddPolicy("AllowSpecificOrigin", policy => { policy.WithOrigins("https://localhost:7149") // Add client origin .WithHeaders("Content-Type", "Authorization", "Any-Custom-Header", "Accept") // Allow specific headers .WithMethods("GET", "POST", "PUT", "DELETE"); // Allow specific methods }); }); // Configure CORS policy to allow specific origins, headers, and methods builder.Services.AddCors(options => { options.AddPolicy("AllowAnotherSpecificOrigin", policy => { policy.WithOrigins("https://example.com") // Add client origin .AllowAnyHeader() // Allow specific headers .WithMethods("GET", "POST", "PUT", "DELETE"); // Allow specific methods }); }); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); //Apply CORS Globally app.UseCors("AllowSpecificOrigin"); app.UseCors("AllowAnotherSpecificOrigin"); app.UseAuthorization(); app.MapControllers(); app.Run(); } } }
Applying CORS at Controller Level:
Next, modify the User Controller as follows. We are applying the AllowSpecificOrigin policy using the [EnableCors] attribute. You might have a different controller where you can apply the other CORS policy.
using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; using UserServiceAPI.Models; namespace UserServiceAPI.Controllers { [EnableCors("AllowSpecificOrigin")] // Apply specific CORS policy to this controller [Route("api/[controller]/[action]")] [ApiController] public class UserController : ControllerBase { private readonly UserRepository _userRepository; public UserController(UserRepository userRepository) { _userRepository = userRepository; } [HttpGet] public async Task<ActionResult<List<User>>> GetAllUsers() { return Ok(await _userRepository.GetAllUsersAsync()); } [HttpGet("{id}")] public async Task<ActionResult<User>> GetUserById(int id) { var user = await _userRepository.GetUserByIdAsync(id); if (user == null) { return NotFound(); } return Ok(user); } [HttpPost] public async Task<ActionResult> AddUser(User user) { await _userRepository.AddUserAsync(user); return CreatedAtAction(nameof(GetUserById), new { id = user.Id }, user); } [HttpPut("{id}")] public async Task<ActionResult> UpdateUser(int id, User user) { if (id != user.Id) { return BadRequest(); } await _userRepository.UpdateUserAsync(user); return NoContent(); } [HttpDelete("{id}")] public async Task<ActionResult> DeleteUser(int id) { await _userRepository.DeleteUserAsync(id); return NoContent(); } } }
The [EnableCors] attribute only applies the specified CORS policy to the controller it decorates. This method is useful when different parts of your API need to interact with different clients or require different CORS settings.
CORS Configuration at Action-Level in ASP.NET Core Web API
You can apply CORS policies at the action method level for even more control. Let us use the same CORS service configuration at the action method level. So, modify the User Controller as follows:
using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; using UserServiceAPI.Models; namespace UserServiceAPI.Controllers { [Route("api/[controller]/[action]")] [ApiController] public class UserController : ControllerBase { private readonly UserRepository _userRepository; public UserController(UserRepository userRepository) { _userRepository = userRepository; } [EnableCors("AllowSpecificOrigin")] [HttpGet] public async Task<ActionResult<List<User>>> GetAllUsers() { return Ok(await _userRepository.GetAllUsersAsync()); } [EnableCors("AllowSpecificOrigin")] [HttpGet("{id}")] public async Task<ActionResult<User>> GetUserById(int id) { var user = await _userRepository.GetUserByIdAsync(id); if (user == null) { return NotFound(); } return Ok(user); } [EnableCors("AllowSpecificOrigin")] [HttpPost] public async Task<ActionResult> AddUser(User user) { await _userRepository.AddUserAsync(user); return CreatedAtAction(nameof(GetUserById), new { id = user.Id }, user); } [EnableCors("AllowAnotherSpecificOrigin")] [HttpPut("{id}")] public async Task<ActionResult> UpdateUser(int id, User user) { if (id != user.Id) { return BadRequest(); } await _userRepository.UpdateUserAsync(user); return NoContent(); } [EnableCors("AllowAnotherSpecificOrigin")] [HttpDelete("{id}")] public async Task<ActionResult> DeleteUser(int id) { await _userRepository.DeleteUserAsync(id); return NoContent(); } } }
At the action level, the [EnableCors] attribute allows us to apply different CORS policies to individual actions within a controller. This is ideal when only specific actions require different CORS settings.
Disabling CORS for Specific Actions
In some instances, you might want to disable CORS for a specific action while it is enabled for others. To do so, you need to use the [DisableCors] attribute. For a better understanding, please modify the User Controller as follows.
using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; using UserServiceAPI.Models; namespace UserServiceAPI.Controllers { [EnableCors("AllowSpecificOrigin")] // Apply CORS policy to the entire controller [Route("api/[controller]/[action]")] [ApiController] public class UserController : ControllerBase { private readonly UserRepository _userRepository; public UserController(UserRepository userRepository) { _userRepository = userRepository; } [HttpGet] public async Task<ActionResult<List<User>>> GetAllUsers() { return Ok(await _userRepository.GetAllUsersAsync()); } [HttpGet("{id}")] public async Task<ActionResult<User>> GetUserById(int id) { var user = await _userRepository.GetUserByIdAsync(id); if (user == null) { return NotFound(); } return Ok(user); } [HttpPost] public async Task<ActionResult> AddUser(User user) { await _userRepository.AddUserAsync(user); return CreatedAtAction(nameof(GetUserById), new { id = user.Id }, user); } [DisableCors] [HttpPut("{id}")] public async Task<ActionResult> UpdateUser(int id, User user) { if (id != user.Id) { return BadRequest(); } await _userRepository.UpdateUserAsync(user); return NoContent(); } [DisableCors] [HttpDelete("{id}")] public async Task<ActionResult> DeleteUser(int id) { await _userRepository.DeleteUserAsync(id); return NoContent(); } } }
The [DisableCors] attribute is used to disable CORS for specific actions while enabling it for the rest of the controller. This is useful for scenarios where you want most actions to be accessible via cross-origin requests but you want to restrict a particular action.
CORS Methods in ASP.NET Core Web API:
- AllowAnyOrigin(): This method allows requests from any origin (domain, protocol, or port). It effectively means that any client, regardless of where it’s hosted, can request your API.
- AllowAnyMethod(): This allows all HTTP methods, including GET, POST, PUT, DELETE, OPTIONS, etc. Your API will accept requests with any method.
- AllowAnyHeader(): This permits any header to be sent in the request. Headers might include things like Content-Type, Authorization, X-Custom-Header, etc.
- WithOrigins(…): This method allows to specify which origins are allowed. For example, WithOrigins(“https://example1.com”, “https://example2.com”)
- WithMethods(…): This method allows to specify which HTTP methods are allowed. For example, WithMethods(“GET”, “POST”)
- WithHeaders(…): This method specifies which headers are allowed. For example, WithHeaders(“Content-Type”, “Authorization”);
CORS is a critical aspect of web application security and cross-domain communication. Understanding and correctly implementing CORS in your ASP.NET Core Web API applications can facilitate secure and efficient cross-origin interactions between front-end and back-end applications.
In the next article, I will discuss how to implement Token-Based Authentication using JWT in ASP.NET Core Web API Application. In this article, I explain CORS (Cross-Origin Resource Sharing) in an ASP.NET Core Web API application with Examples. I hope you enjoy this CORS (Cross-Origin Resource Sharing) in an ASP.NET Core Web API article.