Skip to content

API Management - CORS

This document explains how to configure Cross-Origin Resource Sharing (CORS) using Azure API Management (APIM). Proper CORS setup ensures that your APIs are secure and accessible only from approved origins, reducing the risk of exposing your backend to unintended traffic.

What is CORS?

CORS (Cross-Origin Resource Sharing) is a mechanism that enables web applications running on one domain to access resources from another domain securely. A misconfigured CORS policy - such as simply whitelisting all origins - can lead to security risks and data exposure. This guide provides a structured approach to implement CORS via API Management.

For a more extensive documentation on CORS and how to properly use it, refer to the Microsoft docs at https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS.

Deciding Where to Implement CORS

Azure API Management allows to implement CORS in a straightforward and easy way, removing the need to implement it yourself in your backend. First, a decision needs to be made on where to implement CORS; in your backend or centrally in Azure API Management. Each approach has its advantages and trade-offs.

Configuring CORS in the Backend

Pros:

  • Granular Control:
    Allows for highly specific configurations that are fully tailored to the needs of the API.

  • Direct Integration:
    Many frameworks offer packages that provides middleware that handles CORS.

  • Local Testing:
    Allows for local testing of CORS policies.

Cons:

  • Inconsistency:
    Risk of inconsistent CORS behavior across multiple services if not standardized centrally.

  • Increased Complexity:
    Each service must implement its own CORS logic and enforce it properly, which can lead to duplication of effort.

  • Maintenance Overhead:
    Updating or auditing CORS policies requires changes in multiple codebases.

Configuring CORS in API Management

Pros:

  • Centralized Management:
    Provides a single point of control for CORS across APIs.

  • Consistent Policy Enforcement:
    Ensures uniform CORS behavior across different services.

  • Simplified Backend Code:
    Offloads CORS handling from individual backend applications, reducing complexity.

Cons:

  • Limited Granularity:
    May not offer the fine-grained control available in some backend frameworks, or will complicate API policies to facilitate complex scenarios.

If CORS requirements are pretty straightforward (only allowing credentials to be sent in requests and whitelist some known client URLs), it is easy and reliable to implement CORS using API policies.

if the API requires endpoint-specific rules, the frontends that consume the API are not known beforehand or there are other reason that require close integration with API code, handling CORS at the backend might be more appropriate.

Make an informed decision before proceeding with the implementation.

Implementing CORS

Follow these steps to define a proper CORS policy. APIM allows you to control CORS behavior centrally by adding a CORS policy to your API definitions. Refer to the Microsoft docs at https://learn.microsoft.com/en-us/azure/api-management/cors-policy

While these steps are generic and also apply when CORS is implemented in the backend, this document assumes CORS is being setup using APIM. If not using APIM, refer to the documentation on how to properly setup CORS for your framework of choice.

1. Define Allowed Origins

Specify which origins (domains) are allowed to access your API. Avoid using a wildcard (*) unless absolutely necessary. Only domains that are known to communicate with the API should be added.

2. Specify Allowed Methods

Explicitly state the HTTP methods (e.g., GET, POST, PUT, DELETE) that are permitted. This limits the actions that external clients can perform. Using a wildcard here is usually not that problematic.

3. List Allowed Headers

Include only the headers that are necessary for your API to work. This helps control what information is passed along with the request. Setting a wildcard here is not recommended.

Refer to the list of default allowed headers at https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header, taking into account the additional restrictions that are applicable. In some cases it may be needed to explicitly allow the headers to circumvent these restrictions.

4. Consider the need to allow credentials to be sent

If credentials need to be passed along in the request, additional configuration is needed. Make sure to add the allow-credentials attribute to true: <cors allow-credentials="true">

Additionally, make sure that you whitelist the Authorization header as defined in the previous section.

While not specifically a CORS requirement, note that for frontend applications you may also need to make changes to allow the browser to send credentials along with the request, especially in cross-domain contexts. Refer to https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#including_credentials for more information.

5. List Expose Headers

If your application needs to access the values of response headers (for example when your JavaScript code needs to read the Content-Type of a certain HTTP response), make sure that these headers are exposed to the client. In most cases client access to HTTP response headers is not needed, whitelisting them all using a wildcard therefore is not recommended.

If you API makes use of rate limiting, make sure to also allow access for the client to read headers related to API Management - Rate limiting.

6. Set Preflight Cache Duration

Define the Access-Control-Max-Age to indicate how long the results of a preflight request can be cached. A typical value is around 300 seconds (5 minutes), if not specified it defaults to 0 (no caching).

Define this setting using the preflight-result-max-age attribute on the <allowed-methods> tag.

Example APIM CORS Policy

Below is an example of a CORS policy that you can include in the inbound processing section of your API. Adjust the values based on your specific requirements:

xml
<cors allow-credentials="true">
    <allowed-origins>
        <origin>http://localhost:9999</origin>
        <origin>https://some-frontend-app.azurewebsites.net</origin>
    </allowed-origins>
    <allowed-methods preflight-result-max-age="300">
        <method>GET</method>
        <method>POST</method>
        <method>PUT</method>
        <method>PATCH</method>
        <method>DELETE</method>
        <method>OPTIONS</method>
        <method>HEAD</method>
    </allowed-methods>
    <allowed-headers>
        <header>content-type</header>
        <header>accept</header>
        <header>authorization</header>
    </allowed-headers>
    <expose-headers>
        <header>content-type</header>
    </expose-headers>
</cors>

Explanation:

  • Allowed Origins:
    Only requests from http://localhost:9999 and https://some-frontend-app.azurewebsites.net clients are permitted.

  • Allowed Methods:
    Only the specified HTTP methods GET, POST, PUT, PATCH, DELETE, OPTIONS and HEAD are accepted.

  • Allowed Headers:
    Only Content-Type, Accept and Authorization headers are allowed in the request.

  • Credentials allowed:
    The allow-credentials attribute is set to true, allowing the browser to send credentials in the Authorization header. The Authorization header is whitelisted as well.

  • Exposed Headers:
    The Content-Type header is exposed, so that the client application is allowed to read its' value.****

  • Preflight Cache Duration:
    The preflight result is cached for 300 seconds, reducing unnecessary preflight requests.

Best Practices

  • Least Privilege:
    Allow only the minimal set of origins, methods, and headers required. It is better to start off with a too restricted policy that you need to widen for your API to work rather than simply whitelisting.

  • Regular Reviews:
    Periodically review and update the CORS configuration as your API evolves.

  • Documentation:
    Especially in cases where non-standard choices are made, make sure to document the reasons for widening the CORS scope. This will make regular reviews easier to perform.

Conclusion

Implementing CORS correctly is highly encouraged for maintaining both security and usability of your APIs. By following the structured approach described above, you can ensure that only trusted origins have access to the API.

Ultimately, it's important to note that CORS provides security instructions to the client, and can only be enforced by the client (usually a browser or other clients that may enforce CORS), not by the server. Therefore, while setting up a proper policies does offer a layer of protection for browser based clients, it does not prevent clients such as command line tools from bypassing CORS. Setting proper CORS policies should not be solely relied upon for security.