CORS on IIS7+

Internet Information Services (IIS) 7 and later versions provide multiple approaches for configuring CORS. This guide covers modern, secure CORS implementations using web.config and the IIS CORS Module.

⚠️ Security Warning: Using Access-Control-Allow-Origin: * allows any website to access your resources. Always specify exact origins in production.

IIS Version Compatibility:

  • IIS 7.0+ - Custom headers work in all versions
  • IIS 8.5+ - CORS Module available as extension
  • IIS 10.0+ - Latest version with improved performance

Secure web.config Configuration (Recommended)

For single trusted origin with proper OPTIONS handling:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <!-- SECURE: Specify allowed origin instead of wildcard -->
        <add name="Access-Control-Allow-Origin" value="https://example.com" />
        <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
        <add name="Access-Control-Allow-Headers" value="Content-Type, Authorization" />
        <add name="Access-Control-Max-Age" value="86400" />
        <add name="Vary" value="Origin" />
      </customHeaders>
    </httpProtocol>

    <!-- Handle OPTIONS preflight requests -->
    <handlers>
      <add name="OptionsHandler" verb="OPTIONS" path="*"
           type="System.Web.DefaultHttpHandler"
           resourceType="Unspecified"
           requireAccess="None" />
    </handlers>

    <!-- Return 204 for OPTIONS requests -->
    <rewrite>
      <rules>
        <rule name="CORS Preflight" stopProcessing="true">
          <match url=".*" />
          <conditions>
            <add input="{REQUEST_METHOD}" pattern="^OPTIONS$" />
          </conditions>
          <action type="CustomResponse" statusCode="204" statusReason="No Content" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

Note: The URL Rewrite Module must be installed for preflight handling. Available from Microsoft IIS Downloads or via Web Platform Installer.

Configuration with Credentials

When using credentials (cookies, HTTP authentication), you MUST specify an exact origin:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <!-- MUST use specific origin with credentials -->
        <add name="Access-Control-Allow-Origin" value="https://example.com" />
        <add name="Access-Control-Allow-Credentials" value="true" />
        <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
        <add name="Access-Control-Allow-Headers" value="Content-Type, Authorization" />
        <add name="Access-Control-Max-Age" value="86400" />
        <add name="Vary" value="Origin" />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>

Important: This combination is INVALID per CORS specification:

<!-- INVALID - Will fail in browsers -->
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Credentials" value="true" />

Using IIS CORS Module

For sophisticated origin validation and multiple allowed origins, use the official IIS CORS Module (IIS 8.5+):

Installation

Install via Web Platform Installer or download from Microsoft IIS Extensions.

Configuration

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <cors enabled="true" failUnlistedOrigins="true">
      <add origin="https://example.com"
           allowed="true"
           allowCredentials="true"
           maxAge="86400">
        <allowHeaders>
          <add header="Content-Type" />
          <add header="Authorization" />
        </allowHeaders>
        <allowMethods>
          <add method="GET" />
          <add method="POST" />
          <add method="PUT" />
          <add method="DELETE" />
        </allowMethods>
        <exposeHeaders>
          <add header="Content-Length" />
          <add header="X-Custom-Header" />
        </exposeHeaders>
      </add>
      <add origin="https://app.example.com" allowed="true" />
    </cors>
  </system.webServer>
</configuration>

Integration with ASP.NET

Important: Application-level CORS (in ASP.NET code) takes precedence over web.config headers.

Best Practice:

Public API Configuration

For truly public APIs that don't use credentials:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <!-- WARNING: Only use for public APIs without credentials -->
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
        <add name="Access-Control-Allow-Headers" value="Content-Type" />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</configuration>

If you don't have a web.config file already, create a new file called web.config at the root of your application or site containing one of the examples above.

Common CORS Errors

No 'Access-Control-Allow-Origin' header is present

Solution:

The 'Access-Control-Allow-Origin' header contains multiple values

Solution: Don't set CORS headers in both web.config and application code. Choose one approach.

Credential is not supported if CORS header is '*'

Solution: Replace wildcard with specific origin when using credentials:

<!-- Change from: -->
<add name="Access-Control-Allow-Origin" value="*" />

<!-- To: -->
<add name="Access-Control-Allow-Origin" value="https://example.com" />

Method POST is not allowed by Access-Control-Allow-Methods

Solution: Add the required method to Access-Control-Allow-Methods:

<add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />

Why the Vary Header Matters

The Vary: Origin header prevents cache poisoning when CORS headers change based on the origin. Without it, one origin might receive another origin's cached response.

Always include when:

Performance Considerations

Testing Your CORS Configuration

For comprehensive testing instructions including curl commands, browser DevTools usage, and troubleshooting common CORS errors, see the CORS Testing Guide.

Additional Resources

Who’s behind this

Monsur Hossain and Michael Hausenblas

Contribute

The content on this site stays fresh thanks to help from users like you! If you have suggestions or would like to contribute, fork us on GitHub.

Buy the book

Save 39% on CORS in Action with promotional code hossainco at manning.com/hossain