# CORS configuration

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?

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.

**Note:** Aspire's inner-loop proxy handles port routing between services, but it
  doesn't change browser CORS enforcement. The browser still sees the original
  scheme, host, and port of the API endpoint.

## 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](/get-started/csharp-service-defaults/) project, which supplies `AddServiceDefaults` and `MapDefaultEndpoints`.

```csharp title="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();
```

Configure CORS with the [`cors`](https://github.com/expressjs/cors) middleware in your API's entry script. The complete API examples assume an existing [Express](https://expressjs.com/) app in an _api_ folder:

```json title="api/package.json"
{
  "name": "api",
  "private": true,
  "type": "module",
  "dependencies": {
    "cors": "^2.8.5",
    "express": "^5.1.0"
  }
}
```

```javascript title="api/server.js"
import express from 'express';
import cors from 'cors';

const app = express();

// Configure CORS for development
app.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.

**Caution:** Avoid allowing any origin in production, such as with `AllowAnyOrigin()` in
  .NET or `origin: '*'` in the Node.js `cors` middleware, as it disables CORS
  protection entirely. Always specify the exact origins that should be allowed.

### Using environment-based CORS configuration

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

```csharp title="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:

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

```json title="appsettings.json"
{
  "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:

```javascript title="api/server.js"
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:

```bash title="Development environment"
CORS_ALLOWED_ORIGINS=http://localhost:3000,https://localhost:7000
```

```bash title="Production environment"
CORS_ALLOWED_ORIGINS=https://myapp.com,https://www.myapp.com
```

## 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:

<InstallPackage packageName="Aspire.Hosting.JavaScript" />

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](/integrations/frameworks/javascript/).

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:

```csharp title="AppHost.cs"
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();
```

```typescript title="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:

```csharp title="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();
```

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:

```csharp title="AppHost.cs"
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();
```

```typescript title="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.addNodeApp('api', '../api', 'server.js');
await api.withHttpEndpoint({ env: 'PORT' });
await api.withReference(frontend); // Injects FRONTEND_HTTP into the API
await api.withExternalHttpEndpoints();

await 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:

```javascript title="api/server.js"
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](/fundamentals/service-discovery/).

**Caution:** Don't use the injected service discovery endpoint as a production CORS origin.
  Configure each browser-visible public origin explicitly for the deployed
  environment, in `Cors:AllowedOrigins` for .NET or `CORS_ALLOWED_ORIGINS` for
  the Node.js examples.

## Common CORS issues and solutions

### 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:

```csharp title="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:

```csharp title="Program.cs"
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:

```javascript title="api/server.js"
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:

```javascript title="api/server.js"
app.use(cors({ origin: allowedOrigins }));
app.use(authenticate);
app.get('/secure', requireAuth, (req, res) => res.sendStatus(200));
```

### 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

**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:

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

```javascript title="api/server.js"
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:

```javascript title="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.

**Caution:** When allowing credentials, you must specify exact origins. In .NET, using
  `AllowAnyOrigin()` together with `AllowCredentials()` causes an error. In
  Node.js, browsers reject credentialed responses when the `cors` middleware is
  configured with `origin: '*'`.

### 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

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=*
```

**Note:** `ASPIRE_DASHBOARD_CORS_ALLOWED_ORIGINS` is the AppHost-facing setting. When
  you run the dashboard as a standalone container, configure the equivalent
  `DASHBOARD__OTLP__CORS__ALLOWEDORIGINS` setting instead. For more information,
  see [Enable browser telemetry](/dashboard/enable-browser-telemetry/).

For more information, see [Dashboard configuration](/dashboard/configuration/).

## See also

- [Inner-loop networking overview](/fundamentals/networking-overview/)
- [Service defaults](/get-started/csharp-service-defaults/)
- [JavaScript integration](/integrations/frameworks/javascript/)
- [Dashboard configuration](/dashboard/configuration/)
- [Enable browser telemetry](/dashboard/enable-browser-telemetry/)
- [Cross-Origin Resource Sharing (CORS) in ASP.NET Core](https://learn.microsoft.com/aspnet/core/security/cors)
- [Express `cors` middleware](https://github.com/expressjs/cors)