CORS configuration
Konten ini belum tersedia dalam bahasa Anda.
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.
When does CORS matter with Aspire?
Section titled “When does CORS matter with Aspire?”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.
Configuring CORS in development
Section titled “Configuring CORS in development”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.
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
// Configure CORS for developmentbuilder.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();Configure CORS with the cors middleware in your API’s entry script. The complete API examples assume an existing Express app in an api folder:
{ "name": "api", "private": true, "type": "module", "dependencies": { "cors": "^2.8.5", "express": "^5.1.0" }}import express from 'express';import cors from 'cors';
const app = express();
// Configure CORS for developmentapp.use( cors({ origin: 'http://localhost:3000', // Your frontend origin }));
app.get('/', (req, res) => res.sendStatus(200));
app.listen(process.env.PORT ?? 5000);By default, the cors middleware allows the common HTTP methods and reflects the headers that the browser requests in a preflight.
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:
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:
{ "Cors": { "AllowedOrigins": ["http://localhost:3000", "https://localhost:7000"] }}{ "Cors": { "AllowedOrigins": ["https://myapp.com", "https://www.myapp.com"] }}This example reads a comma-separated list of origins from the CORS_ALLOWED_ORIGINS environment variable. If the variable isn’t set, no cross-origin requests are allowed:
import express from 'express';import cors from 'cors';
const allowedOrigins = (process.env.CORS_ALLOWED_ORIGINS ?? '') .split(',') .map((origin) => origin.trim()) .filter(Boolean);
const app = express();
app.use(cors({ origin: allowedOrigins }));
app.get('/', (req, res) => res.sendStatus(200));
app.listen(process.env.PORT ?? 5000);Then set the allowed origins in each environment where the API runs, for example in your hosting platform’s application settings:
CORS_ALLOWED_ORIGINS=http://localhost:3000,https://localhost:7000CORS_ALLOWED_ORIGINS=https://myapp.com,https://www.myapp.comUsing 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 add javascriptAspire CLI interaktif; pilih hasil pencarian yang sesuai saat diminta:
Select an integration to add:
> javascript (Aspire.Hosting.JavaScript)> Other results listed as selectable options...#:package Aspire.Hosting.JavaScript@*<PackageReference Include="Aspire.Hosting.JavaScript" Version="*" />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:
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 APIawait api.withExternalHttpEndpoints();
await builder.build().run();var builder = DistributedApplication.CreateBuilder(args);
var frontend = builder.AddJavaScriptApp("frontend", "../frontend", "dev") .WithHttpEndpoint(port: 3000, env: "PORT");
var api = builder.AddProject<Projects.Api>("api") .WithReference(frontend) // Injects services__frontend__http__0 into the API .WithExternalHttpEndpoints();
builder.Build().Run();In the API project, read the injected frontend endpoint for CORS configuration:
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();This example assumes the Express API from the previous examples in an api folder with a server.js entry script. Add the frontend and the API to your AppHost. The API listens on the PORT value that Aspire assigns:
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.addNodeApp('api', '../api', 'server.js');await api.withHttpEndpoint({ env: 'PORT' });await api.withReference(frontend); // Injects FRONTEND_HTTP into the APIawait api.withExternalHttpEndpoints();
await builder.build().run();var builder = DistributedApplication.CreateBuilder(args);
var frontend = builder.AddJavaScriptApp("frontend", "../frontend", "dev") .WithHttpEndpoint(port: 3000, env: "PORT");
var api = builder.AddNodeApp("api", "../api", "server.js") .WithHttpEndpoint(env: "PORT") .WithReference(frontend) // Injects FRONTEND_HTTP into the API .WithExternalHttpEndpoints();
builder.Build().Run();In the API, read the injected frontend endpoint for CORS configuration. Aspire sets NODE_ENV to development for Node.js apps when the AppHost runs in the Development environment:
import express from 'express';import cors from 'cors';
// Use the Aspire-injected endpoint only during local development.const frontendUrl = process.env.FRONTEND_HTTP;
let allowedOrigins;if (process.env.NODE_ENV === 'development' && URL.canParse(frontendUrl)) { allowedOrigins = [new URL(frontendUrl).origin];} else { // Production configuration must contain the browser-visible public origins. allowedOrigins = (process.env.CORS_ALLOWED_ORIGINS ?? '') .split(',') .map((origin) => origin.trim()) .filter(Boolean);}
const app = express();
app.use(cors({ origin: allowedOrigins }));
app.get('/', (req, res) => res.sendStatus(200));
app.listen(process.env.PORT ?? 5000);Aspire also injects the same endpoint as services__frontend__http__0. For more information, see Service discovery.
Common CORS issues and solutions
Section titled “Common CORS issues and solutions”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:
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:
app.UseRouting();app.UseCors();app.UseAuthentication();app.UseAuthorization();app.MapGet("/secure", () => Results.Ok()).RequireAuthorization();Cause: The order of middleware registration matters. Express runs middleware and routes in the order you register them, so a route or middleware registered before cors() can respond without CORS headers.
Solution: Register the cors middleware before your routes:
app.use(cors({ origin: allowedOrigins }));app.get('/', (req, res) => res.sendStatus(200));If you’re using authentication middleware, register it after CORS so preflight requests can be handled first. This fragment assumes existing authenticate and requireAuth middleware functions:
app.use(cors({ origin: allowedOrigins }));app.use(authenticate);app.get('/secure', requireAuth, (req, res) => res.sendStatus(200));Issue: Preflight requests failing
Section titled “Issue: Preflight requests failing”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.
OPTIONS requests return 200 OK, but the response has no Access-Control-Allow-* headers.
Cause: The cors middleware doesn’t run for OPTIONS requests. Express answers an OPTIONS request for a route automatically, but that response doesn’t include CORS headers.
Solution: When you register it for all routes with app.use(cors(...)), the cors middleware responds to preflight requests automatically. If you apply cors() to individual routes instead, also register it for the matching OPTIONS requests with app.options(...). Ensure the configuration allows the request’s origin, method, and headers, and that the middleware runs before authentication and your routes.
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:
options.AddDefaultPolicy(policy =>{ policy.WithOrigins("https://myapp.com") .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); // Enable credentials});app.use( cors({ origin: 'https://myapp.com', credentials: true, // 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:
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.
Dashboard CORS configuration
Section titled “Dashboard CORS configuration”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.comYou can also use the * wildcard to allow any domain (development only):
ASPIRE_DASHBOARD_CORS_ALLOWED_ORIGINS=*For more information, see Dashboard configuration.