CORS configuration

Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.

Cross-Origin Resource Sharing (CORS) is a browser security feature that restricts how web pages can request resources from a different domain. This article explains when CORS is relevant in Aspire applications, and how to configure it correctly for development and production.

CORS is only enforced by browsers. It applies when a web frontend, running in a user’s browser, makes HTTP requests to an API on a different origin. That origin could be a different host, port, or scheme. Aspire doesn’t add or remove any browser CORS restrictions.

You need to configure CORS in your API when:

  • Your Blazor WebAssembly, React, Angular, or other browser-based frontend calls an API on a different port or host.
  • Your API is hosted separately from your frontend during development or production.
  • Your browser shows errors like Access to fetch at 'http://localhost:5000' from origin 'http://localhost:3000' has been blocked by CORS policy.

You don’t need to configure CORS when:

  • The API request is made on the server, for example by a Blazor Server, Razor Pages, or MVC application. Browser-side requests from those applications still require CORS if they cross origins.
  • Your API and frontend share the same origin, consisting of the same scheme, host, and port.
  • Communication is server-to-server. That is, backend services calling other backend services.

During development, the recommended approach is to allow your frontend’s development server origin. Configure CORS in your API’s startup code.

The examples on this page use three sets of tabs, which you choose independently:

  • Aspire CLI, apphost.cs (C# file-based app), and PackageReference(*.csproj) tabs select an installation method for Aspire integrations. If you’re using a TypeScript AppHost, only the Aspire CLI tab is applicable.
  • .NET and Node.js tabs select the language of the API that the AppHost orchestrates.
  • C# and TypeScript tabs select the language of the AppHost itself.

Configure CORS with AddCors and UseCors in your API’s Program.cs. The complete API examples assume an existing ASP.NET Core project with a reference to your shared service defaults project, which supplies AddServiceDefaults and MapDefaultEndpoints.

Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
// Configure CORS for development
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.WithOrigins("http://localhost:3000") // Your frontend origin
.AllowAnyHeader()
.AllowAnyMethod();
});
});
var app = builder.Build();
app.UseRouting();
app.UseCors();
app.MapDefaultEndpoints();
app.MapGet("/", () => Results.Ok());
app.Run();

Using environment-based CORS configuration

Section titled “Using environment-based CORS configuration”

A better pattern is to read allowed origins from configuration, so you can easily change them per environment:

Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
var allowedOrigins = builder.Configuration
.GetSection("Cors:AllowedOrigins")
.Get<string[]>() ?? [];
policy.WithOrigins(allowedOrigins)
.AllowAnyHeader()
.AllowAnyMethod();
});
});
var app = builder.Build();
app.UseRouting();
app.UseCors();
app.MapDefaultEndpoints();
app.MapGet("/", () => Results.Ok());
app.Run();

Then configure the allowed origins per environment:

appsettings.Development.json
{
"Cors": {
"AllowedOrigins": ["http://localhost:3000", "https://localhost:7000"]
}
}

Using Aspire to inject the frontend URL locally

Section titled “Using Aspire to inject the frontend URL locally”

During local orchestration, you can use Aspire’s environment variable injection to provide the frontend endpoint to your API. Scope this approach to development because the injected endpoint reflects the resource’s network context, which might not be the public origin used by a browser after deployment.

Install the JavaScript hosting integration in your AppHost:

Aspire CLI — Aspire.Hosting.JavaScript Paket hinzufügen
aspire add javascript

Die Aspire CLI ist interaktiv; das passende Suchergebnis wählen, wenn gefragt:

Aspire CLI — Beispielausgabe
Select an integration to add:
> javascript (Aspire.Hosting.JavaScript)
> Other results listed as selectable options...

These examples assume a JavaScript frontend in ../frontend with a dev script in its package.json. That script must start an HTTP server listening on the injected PORT value. Adjust the paths to match your application; for framework-specific setup, see JavaScript integration.

This example assumes an existing Api project. A C# AppHost also needs a project reference to Api. Add the frontend and the API to your AppHost:

apphost.mts
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const frontend = await builder.addJavaScriptApp('frontend', '../frontend', {
runScriptName: 'dev',
});
await frontend.withHttpEndpoint({ port: 3000, env: 'PORT' });
const api = await builder.addProject('api', '../Api/Api.csproj');
await api.withReference(frontend); // Injects services__frontend__http__0 into the API
await api.withExternalHttpEndpoints();
await builder.build().run();

In the API project, read the injected frontend endpoint for CORS configuration:

Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
// Use the Aspire-injected endpoint only during local development.
var frontendUrl = builder.Configuration["services:frontend:http:0"];
if (builder.Environment.IsDevelopment() &&
Uri.TryCreate(frontendUrl, UriKind.Absolute, out var frontendUri))
{
policy.WithOrigins(frontendUri.GetLeftPart(UriPartial.Authority))
.AllowAnyHeader()
.AllowAnyMethod();
}
else
{
// Production configuration must contain the browser-visible public origins.
var allowedOrigins = builder.Configuration
.GetSection("Cors:AllowedOrigins")
.Get<string[]>() ?? [];
policy.WithOrigins(allowedOrigins)
.AllowAnyHeader()
.AllowAnyMethod();
}
});
});
var app = builder.Build();
app.UseRouting();
app.UseCors();
app.MapDefaultEndpoints();
app.MapGet("/", () => Results.Ok());
app.Run();

Issue: CORS error despite configuring CORS middleware

Section titled “Issue: CORS error despite configuring CORS middleware”

Symptom: Browser shows a CORS error even though you’ve added CORS middleware to the API.

Cause: The order of middleware execution matters. CORS must run after routing and before authorization and endpoint execution. In minimal WebApplication apps, calls such as MapGet and MapDefaultEndpoints register endpoints; their position relative to UseCors doesn’t determine middleware execution order.

Solution: Place CORS between routing and endpoint execution. Minimal hosting adds routing automatically when needed; an explicit UseRouting makes that ordering visible:

Program.cs
app.UseRouting();
app.UseCors();
app.MapGet("/", () => Results.Ok());

If you’re using authentication and authorization, place their middleware after CORS so preflight requests can be handled first. This fragment assumes the corresponding authentication and authorization services are already configured:

Program.cs
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/secure", () => Results.Ok()).RequireAuthorization();

Symptom: The browser blocks a request after its preflight OPTIONS request fails.

OPTIONS requests return 405 Method Not Allowed.

Cause: The API doesn’t handle OPTIONS requests, which browsers use for CORS preflight checks.

Solution: UseCors() handles recognized preflight requests automatically. Ensure an applicable policy allows the request’s origin, method, and headers, and that CORS runs after routing and before authorization and endpoint execution.

Issue: Credentials not sent with cross-origin requests

Section titled “Issue: Credentials not sent with cross-origin requests”

Symptom: Cookies aren’t included in cross-origin requests.

Cause: By default, browser fetch requests don’t include cookies on cross-origin requests.

Solution: Enable credentials on both the server and the client. In the API’s existing CORS configuration, use an explicit origin allowlist:

Program.cs
options.AddDefaultPolicy(policy =>
{
policy.WithOrigins("https://myapp.com")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials(); // Enable credentials
});

In a browser JavaScript module (or inside an async function), opt into credentials. Replace the API URL with your application’s browser-visible URL:

frontend.js
const response = await fetch('https://api.myapp.com/', {
credentials: 'include',
});

Cookie policies still apply: cross-site cookies generally need SameSite=None and Secure, and browser third-party-cookie restrictions can prevent their delivery.

An explicitly supplied bearer Authorization header is different: allow that header in the CORS policy and handle its preflight. It doesn’t inherently require AllowCredentials() (or credentials: true in the Node.js cors middleware); add the header explicitly in the client request.

Issue: CORS errors in production but not development

Section titled “Issue: CORS errors in production but not development”

Symptom: CORS works locally but fails in production.

Cause: The production origin (for example, https://myapp.com) is not in the allowed origins list, or the environment-specific configuration isn’t deployed.

Solution: Verify the allowed origins in your production configuration match the exact origin your frontend uses, including the scheme (https://) and any subdomain.

The Aspire dashboard collects OpenTelemetry data from browser apps (browser telemetry). When a browser app runs on a different origin than the dashboard — for example, on a custom localhost domain — the dashboard must allow that origin so the browser can post telemetry to it.

In the AppHost, set the ASPIRE_DASHBOARD_CORS_ALLOWED_ORIGINS environment variable to a comma-separated list of allowed origins:

ASPIRE_DASHBOARD_CORS_ALLOWED_ORIGINS=https://myapp.com,https://www.myapp.com

You can also use the * wildcard to allow any domain (development only):

ASPIRE_DASHBOARD_CORS_ALLOWED_ORIGINS=*

For more information, see Dashboard configuration.