Implementing CORS in an ASP.NET Core API for Angular Apps
An Angular application and an ASP.NET Core API often run on different origins during development. Angular’s development server commonly uses http://localhost:4200, while the API may listen on https://localhost:5001 or another port. Even when both applications are running on the same computer, the browser treats those origins as different and applies its cross-origin security rules.
Cross-Origin Resource Sharing, usually called CORS, provides a controlled way for the API to tell browsers which front-end origins may send requests. It is a browser policy rather than an Angular or HTTP client feature. Configuring it correctly allows requests while preserving a useful boundary around your API.
This matters in Australian development teams as much as anywhere else. A developer in Melbourne might run Angular locally while connecting to an API hosted in an Australian Azure region, whereas a production application could be served through a CDN with a completely different hostname. The origin must match the actual scheme, host, and port used by the browser.
A reliable implementation begins with an explicit policy, continues with correct middleware ordering, and finishes with deployment-specific configuration. Avoiding a permissive wildcard in production is especially important when the API handles customer data, authentication cookies, or internal business operations.
Understand How Browser Origins Work
An origin consists of a scheme, host, and port. For example, https://portal.example.com and https://api.example.com have different origins because their hosts differ. Likewise, http://localhost:4200 and http://localhost:5000 are different origins, even though both use localhost.
When Angular calls the API, the browser evaluates the API’s response headers. A successful CORS response generally includes Access-Control-Allow-Origin with the requesting origin. If the request is more complex, the browser first sends an OPTIONS preflight request to ask whether the method and headers are permitted.
Simple requests may avoid a preflight, but requests using methods such as PUT, PATCH, or DELETE, or headers such as Authorization and certain custom headers, commonly trigger one. The API must respond to that preflight request before the browser sends the actual call. A server can technically return data while the browser still blocks Angular from reading it.
CORS is enforced by browsers, not by tools such as curl or server-side .NET code. A request made in Postman may appear to work while the same call fails in Chrome or Edge. Test the complete browser path, including the exact front-end origin and any credentials, rather than relying only on an API client.
Define A Focused CORS Policy
In a modern ASP.NET Core application, register a named policy in Program.cs. Keeping the policy named makes its purpose clear and allows different policies to be used for separate API areas if the application grows.
var builder = WebApplication.CreateBuilder(args);
var angularOrigin = builder.Configuration["Cors:AngularOrigin"]
?? "http://localhost:4200";
builder.Services.AddCors(options =>
{
options.AddPolicy("AngularClient", policy =>
{
policy.WithOrigins(angularOrigin)
.AllowAnyHeader()
.AllowAnyMethod();
});
});
builder.Services.AddControllers();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseCors("AngularClient");
app.UseAuthorization();
app.MapControllers();
app.Run();
WithOrigins requires an exact origin. Do not add a trailing slash to the value, and do not include a path such as /dashboard. https://localhost:4200 and http://localhost:4200 are separate origins, so the scheme must also be correct.
A focused policy is preferable to AllowAnyOrigin, particularly for production APIs. A wildcard allows every website to make browser requests to the API, which may be inappropriate even when the API itself does not currently expose sensitive information.
Useful policy decisions to make before writing configuration include:
- The exact Angular development and production origins
- Whether
Authorizationor custom request headers are required - Which HTTP methods the front end genuinely uses
- Whether browser credentials such as cookies must be included
For multiple known environments, add each approved origin explicitly:
policy.WithOrigins(
"http://localhost:4200",
"https://portal.example.com",
"https://portal-staging.example.com")
.AllowAnyHeader()
.AllowAnyMethod();
Do not build an origin string from arbitrary request input and feed it into WithOrigins. If origins need to change by environment, store them in configuration and validate the values during deployment.
Place Middleware In The Correct Order
CORS middleware must run before endpoint execution. In the minimal hosting model, placing app.UseCors after app.MapControllers() is too late for the intended pipeline. A common arrangement is HTTPS redirection, routing where explicitly used, CORS, authentication, authorisation, and endpoint mapping.
For applications using the older startup style, the equivalent arrangement is typically:
public void Configure(IApplicationBuilder app)
{
app.UseRouting();
app.UseCors("AngularClient");
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Authentication and CORS solve different problems. CORS decides whether a browser may make or read a cross-origin request. Authentication determines who the caller is. A request can pass CORS and still receive 401 Unauthorized; it can also be authenticated successfully while the browser refuses to expose the response because CORS headers are missing.
If the API uses endpoint routing, applying the named policy globally is often the simplest approach. Alternatively, a policy can be attached to a controller or action with [EnableCors("AngularClient")]. Avoid combining a broad global policy with inconsistent endpoint-level policies unless there is a clear reason, because the resulting behaviour can be difficult to audit.
Preflight requests should generally be handled by the CORS middleware without custom controller code. If an upstream proxy, authentication handler, or rewrite rule intercepts OPTIONS requests first, the browser may receive a redirect or a 401 response instead of the required CORS headers.
Connect Angular To The API
Angular’s environment configuration should contain the API base URL, while the browser’s origin remains the address from which Angular was loaded. For local development, an Angular service might use:
@Injectable({ providedIn: 'root' })
export class ProductsService {
private readonly apiUrl = `${environment.apiUrl}/api/products`;
constructor(private http: HttpClient) {}
getProducts() {
return this.http.get<Product[]>(this.apiUrl);
}
}
If the Angular app runs at http://localhost:4200 and the API runs at https://localhost:5001, the API’s CORS policy must allow the former. The API URL is the destination; it is not the origin that should be placed in WithOrigins.
Some teams use an Angular development proxy so browser requests appear to target the same origin locally. That can reduce local CORS noise, but it does not remove the need for a correct production policy. Once the deployed browser application and API use separate domains, the production origin must be configured explicitly.
When diagnosing front-end integration problems, compare the browser’s Network tab with the Angular code. Resources such as Oscar David Sanchez can also provide useful perspectives on web development patterns, but the decisive evidence for a CORS issue is usually the request URL, Origin header, response status, and response headers.
Credentialed requests need additional configuration. If Angular sends cookies, use:
this.http.get<Order[]>(url, { withCredentials: true });
The server policy must then use an explicit origin and AllowCredentials():
policy.WithOrigins("https://portal.example.com")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
AllowCredentials() cannot be combined with AllowAnyOrigin(). This restriction prevents a server from authorising credentials for every possible website. Cookie settings, including SameSite, Secure, domain, and path, must also be correct; CORS alone does not make cookies travel between sites.
Diagnose Preflight And Header Errors
The browser console often reports a CORS error without exposing the underlying server response to JavaScript. Inspect the failed request in the Network tab and look for an OPTIONS request immediately before the actual call. Check the request’s Origin, Access-Control-Request-Method, and Access-Control-Request-Headers values.
A useful preflight response should include headers similar to:
Access-Control-Allow-Origin: http://localhost:4200
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
The exact set of headers depends on the policy and the request. If Angular sends Authorization, the API must permit that request header. If the API redirects HTTP to HTTPS during preflight, the browser may reject the redirect; use the correct HTTPS API URL in the Angular environment instead.
Common symptoms usually point to a small set of causes:
- Missing
Access-Control-Allow-Origin: the policy did not run, or the origin was not allowed - Preflight returns
401or403: authentication or proxy handling is interceptingOPTIONS - Method is not permitted: the policy does not allow the requested verb
- Header is not permitted: a client header is absent from the allowed-header list
- Credentials error: a wildcard origin is being used with cookies or
withCredentials
A reverse proxy can change the effective public origin. For example, an API behind Azure App Service, Nginx, or an Australian hosting provider may receive forwarded requests while users access it through a public hostname. Confirm the browser-visible origin and inspect the response after the proxy has processed it.
For repeatable testing, send an Origin header with curl:
curl -i -X OPTIONS https://api.example.com/api/products \
-H "Origin: https://portal.example.com" \
-H "Access-Control-Request-Method: GET" \
-H "Access-Control-Request-Headers: Authorization"
This does not reproduce browser enforcement, but it reveals whether the server returns the expected preflight headers.
Harden CORS For Production
Development and production should not share an accidental localhost policy. Store the approved origin in appsettings.Development.json, environment variables, Azure App Configuration, or another deployment system. A production API serving customers in Sydney, Perth, or Brisbane should allow the deployed portal hostname, not the developer’s local address.
You can bind several origins from configuration:
{
"Cors": {
"Origins": [
"http://localhost:4200",
"https://portal.example.com"
]
}
}
Then register them after validation:
var origins = builder.Configuration
.GetSection("Cors:Origins")
.Get<string[]>() ?? Array.Empty<string>();
builder.Services.AddCors(options =>
{
options.AddPolicy("AngularClient", policy =>
{
policy.WithOrigins(origins)
.AllowAnyHeader()
.AllowAnyMethod();
});
});
Use a narrow list for customer-facing systems. Allow only the methods and headers the application needs when the API has a high security requirement. CORS is not a substitute for authorisation, anti-forgery protections, input validation, rate limiting, or secure token storage, but a restrictive origin policy reduces the number of websites that can interact with the API through a user’s browser.
The same separation applies to supporting development tools and open-source experiments. For example, a data-collection project using a Puppeteer Sharp scraper may make server-side requests and therefore have no browser CORS requirement at all. Identify whether a call originates in a browser before adding server configuration.
Keep a short deployment checklist for every Angular and ASP.NET Core release:
- Confirm the public Angular origin, including HTTPS and any non-standard port
- Verify preflight responses through the production proxy or load balancer
- Check credential, cookie, and token behaviour in a real browser
- Remove temporary localhost and wildcard entries when no longer needed
A well-defined CORS policy should become ordinary application configuration rather than a last-minute workaround. Set the allowed origins from environment-specific settings, verify middleware order, test preflight requests, and inspect the deployed browser flow. These steps let an Angular client communicate cleanly with an ASP.NET Core API while keeping cross-origin access limited to the applications you actually operate.