Appearance
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 fromhttp://localhost:9999andhttps://some-frontend-app.azurewebsites.netclients are permitted.Allowed Methods:
Only the specified HTTP methodsGET,POST,PUT,PATCH,DELETE,OPTIONSandHEADare accepted.Allowed Headers:
OnlyContent-Type,AcceptandAuthorizationheaders are allowed in the request.Credentials allowed:
Theallow-credentialsattribute is set totrue, allowing the browser to send credentials in theAuthorizationheader. TheAuthorizationheader is whitelisted as well.Exposed Headers:
TheContent-Typeheader 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.