GuidesPLATFORMS

Configure CORS in Azure

Azure does not provide a single global switch for cross-origin access. The setting lives in a different place per service. Configuring Azure CORS means applying platform settings in App Service and Functions, per-service rules in Azure Storage, or an XML policy in API Management.

App Service

In the Azure portal, open the App Service app and navigate to the CORS page located under the API section of the left menu. This page manages the Allowed origins list. Once configured, the App Service platform intercepts and answers preflight OPTIONS requests before they reach your application runtime. Do not combine platform CORS with in-app CORS middleware such as ASP.NET Core UseCors; choose one mechanism to prevent conflicting headers.

Manage App Service CORS with the Azure CLI
# Append an origin to the allowed origins list
az webapp cors add -g my-resource-group -n my-api \
  --allowed-origins https://app.example.com

# Read back the effective list
az webapp cors show -g my-resource-group -n my-api

# Remove one origin
az webapp cors remove -g my-resource-group -n my-api \
  --allowed-origins https://app.example.com

# Allow every origin: pass "*" after removing all other entries

The az webapp cors add command appends origins to the existing list, az webapp cors remove deletes specified entries, and az webapp cors show returns the current configuration. To allow all origins, set the allowed origin to * and remove all other origin entries from the list.

Azure Functions

Deployed function apps rely on the same underlying App Service platform CORS configuration. Populating the Allowed origins list automatically appends the Access-Control-Allow-Origin header to every response emitted by the function app HTTP endpoints. Manage these entries using az functionapp cors add, az functionapp cors remove, and az functionapp cors show. To permit credentials on responses, run az functionapp cors credentials --enable true to return Access-Control-Allow-Credentials.

Local development uses a completely separate configuration that never deploys to Azure. The Host.CORS setting in local.settings.json controls only the local func host runtime.

local.settings.json — local func host only, never published
{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "node"
  },
  "Host": {
    "LocalHttpPort": 7071,
    "CORS": "http://localhost:5173,https://app.example.com",
    "CORSCredentials": false
  }
}

# Deployed app instead: az functionapp cors add -g <rg> -n <app> \
#   --allowed-origins https://app.example.com
# Credentials: az functionapp cors credentials -g <rg> -n <app> --enable true

  • The local CORS value must be formatted as a comma-separated list of origins with no spaces.
  • On a deployed function app, the wildcard * entry is ignored if any other explicit domain entry exists in the list.
  • Setting CORSCredentials to true allows withCredentials requests during local execution.

Azure Storage

Azure Storage rules apply individually per service: Blob, Queue, Table, and File each maintain their own independent rule sets, which are disabled by default. You can define up to five rules per service. CORS is not an authorization mechanism; cross-origin requests still require valid authorization or a publicly accessible resource.

CORS rules are set per storage service
# Allow the app origin to read and upload blobs (--services: b f q t)
az storage cors add --account-name mystorageaccount --services b \
  --origins 'https://app.example.com' \
  --methods GET PUT \
  --allowed-headers '*' \
  --exposed-headers '*' \
  --max-age 3600

# Inspect or clear the rules per service
az storage cors list --account-name mystorageaccount --services b
az storage cors clear --account-name mystorageaccount --services b

  • Evaluation order matters: the first rule matching the incoming origin and HTTP method is selected, and request headers are evaluated strictly against that rule's AllowedHeaders. List restrictive rules first.
  • A preflight request sent to a service with CORS disabled, or matching no rule, returns a 403 Forbidden status.
  • An OPTIONS request missing either the Origin header or the Access-Control-Request-Method header returns 400 Bad Request.
  • The --max-age flag specifies the duration in seconds that browsers may cache the preflight response, defaulting to 0.

API Management

API Management handles cross-origin requests through an inbound cors policy defined in XML. The policy can be attached at the global, product, API, or operation scope. Only the cors policy is evaluated on the OPTIONS preflight; the remaining policies run on the approved request.

Inbound policy XML in API Management
<cors allow-credentials="false">
    <allowed-origins>
        <origin>https://app.example.com</origin>
    </allowed-origins>
    <allowed-methods preflight-result-max-age="300">
        <method>GET</method>
        <method>POST</method>
    </allowed-methods>
    <allowed-headers>
        <header>content-type</header>
    </allowed-headers>
</cors>

<!-- Scopes: global, product, API, or operation. Only this policy
     runs on the OPTIONS preflight; the rest run on the approved call. -->

  • If an API defines an explicit OPTIONS operation, the gateway skips the cors policy preflight logic, and that operation must construct and return the preflight response headers directly.
  • A cors policy configured at product scope fails when the subscription key is passed in a request header, because preflight requests carry no credentials. Pass the subscription key as a query parameter instead.
  • The allowed-methods element is required once you allow methods other than GET and POST.

Confirm the wildcard and credentials rules

Browsers reject any response that combines Access-Control-Allow-Origin: * with a credentialed request. Applications sending cookies or Authorization headers must define explicit origins and enable credentials on the hosting service: set the supportCredentials property or portal checkbox in App Service, run az functionapp cors credentials for Azure Functions, or set the allow-credentials attribute in API Management. Azure Storage has no cookie-based credential mode.

An origin list that works for anonymous GET requests still fails for credentialed apps until the explicit origin and credentials pair is configured.

Verify with curl

Inspect the raw response headers from both the standard request and the preflight probe. The actual response must return access-control-allow-origin, while the preflight probe must return an HTTP 2xx status alongside access-control-allow-origin, access-control-allow-methods, and access-control-allow-headers.

Probe the actual request and the preflight
# Actual request: expect access-control-allow-origin on the response
curl -i -H "Origin: https://app.example.com" \
  https://my-api.azurewebsites.net/api/data

# Preflight: expect 2xx plus allow-origin, allow-methods, allow-headers
curl -i -X OPTIONS \
  -H "Origin: https://app.example.com" \
  -H "Access-Control-Request-Method: POST" \
  https://my-api.azurewebsites.net/api/data

If the preflight returns 403 on Azure Storage, re-check the rule order and header matching. If API Management returns an empty 200 without CORS headers, verify the scope at which the policy was applied. If the header is missing entirely, verify that the setting was saved on the same service and app as the URL under test, and paste the response into the header checker to evaluate the rules.

Good questions.

Why does Azure Storage return a 403 status code for preflight requests?

Azure Storage returns 403 Forbidden when CORS is disabled on the target service (Blob, Queue, Table, or File) or when no rule matches the incoming Origin and Access-Control-Request-Method. Rules are set per service with az storage cors add.

Why does API Management fail preflight checks when using product-level CORS?

Preflight OPTIONS requests do not carry credentials such as subscription keys. When the key travels in a header, the unauthenticated preflight fails before it can succeed. Pass the subscription key as a query parameter instead.

Can App Service platform CORS be combined with ASP.NET Core UseCors middleware?

No. Combining the platform configuration with application middleware such as ASP.NET Core UseCors causes conflicting or duplicated headers. Choose either platform CORS or application middleware.

Why does my deployed function app ignore the wildcard origin?

On a deployed function app, the wildcard * entry is ignored whenever any other explicit domain entry exists in the Allowed origins list. Remove the other entries or replace the wildcard with explicit origins.