You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: samples/dynamic-cors/README.md
+28-22Lines changed: 28 additions & 22 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,7 +10,7 @@ Implement dynamic, per-API CORS origin validation in Azure API Management using
10
10
11
11
1. Understand why the built-in APIM `<cors>` policy cannot support fully dynamic origin validation and how to replace it with custom policy fragments.
12
12
1. Build a reusable policy fragment that evaluates the `Origin` header against a per-API allowed-origins mapping, handling both OPTIONS preflight and actual request CORS headers.
13
-
1. Compare four mapping strategies side-by-side: **native `<cors>` policy** (Baseline), **hard-coded** (Phase 1), **Named Values** (Phase 2), and **cache-backed** (Phase 3), understanding the trade-offs of each.
13
+
1. Compare five mapping strategies side-by-side: **native `<cors>` policy** (Baseline), **hard-coded** (Phase 1), **Named Values** (Phase 2), **cache-backed** (Phase 3), and **per-API cache** (Phase 4), understanding the trade-offs of each.
14
14
1. Use an admin API (`/admin/load-cache`) to populate the APIM internal cache at runtime, demonstrating the `/admin/` convention for operational endpoints.
15
15
1. Verify CORS behaviour with automated tests covering allowed origins, disallowed origins, missing `Origin` headers, and fail-closed cache behaviour.
16
16
@@ -29,52 +29,58 @@ You need a single, reusable CORS mechanism that can be applied to any API while
29
29
30
30
This lab deploys all phases **side-by-side** so you can inspect and compare them without redeployment:
31
31
32
-
-**Nine APIs** (two per phase plus an admin API) with no backends. Each CORS demo API includes a GET operation returning a JSON response indicating whether CORS was allowed and an OPTIONS operation for preflight handling.
32
+
-**Eleven APIs** (two per phase plus an admin API) with no backends. Each CORS demo API includes a GET operation returning a JSON response indicating whether CORS was allowed and an OPTIONS operation for preflight handling.
-**Admin** (`cors-admin`) - `POST /load-cache/{cacheKey}` stores a value in the APIM internal cache and `POST /clear-cache/{cacheKey}` removes it (subscription required).
38
39
39
40
> [!IMPORTANT]
40
41
> **Production security:** The admin API in this sample is protected by a subscription key only. Subscription keys are shared secrets and are not a substitute for identity-based authentication. In production, you should add `validate-azure-ad-token` or `validate-jwt` to the admin API's inbound policy. See the [authX](../authX/) and [authX-pro](../authX-pro/) samples for implementation patterns. The policy XML includes a commented example of where to place the validation.
41
42
42
-
-**Three APIM policy fragments** (one per dynamic phase) demonstrating progressively more flexible origin-mapping strategies:
43
+
-**Four APIM policy fragments** (one per dynamic phase) demonstrating progressively more flexible origin-mapping strategies:
43
44
-`DynamicCorsHardcoded` - origins embedded in a C# `switch` expression.
44
45
-`DynamicCorsNamedValues` - origins read from an APIM Named Value as JSON.
45
-
-`DynamicCorsCached` - origins read from the APIM internal cache. Returns `503` if the cache is not initialized (fail-closed).
46
+
-`DynamicCorsCached` - origins read from the APIM internal cache as a single JSON mapping. Returns `503` if the cache is not initialized (fail-closed).
47
+
-`DynamicCorsCachedPerApi` - origins read from per-API cache entries (`corsOriginMapping-{apiId}`). Returns `503` if the current API's cache entry is missing (fail-closed).
46
48
-**One Named Value** (`CorsOriginMapping`) holding the JSON origin mapping for Phase 2.
47
49
- An **API-level policy** (`cors-api-policy.xml`) that includes the active CORS fragment in `<inbound>` and documents the outbound pattern for APIs with real backends.
|**Baseline**| Native `<cors>`| Static XML attribute list | Same origins for all APIs; cannot vary per API |
54
-
|**Phase 1**|`DynamicCorsHardcoded` fragment | Inline `switch/case` in C# | Per-API control; requires redeploying the fragment to change origins |
55
-
|**Phase 2**|`DynamicCorsNamedValues` fragment | JSON string in a Named Value | Updateable in the portal; **4,096-char limit** per Named Value |
56
-
|**Phase 3**|`DynamicCorsCached` fragment + admin API | APIM internal cache | No size limit; updated via admin API; fail-closed when cache is empty; can swap to external Redis |
|**Baseline**| Native `<cors>`| Static XML attribute list | Same origins for all APIs; cannot vary per API |
56
+
|**Phase 1**|`DynamicCorsHardcoded` fragment | Inline `switch/case` in C# | Per-API control; requires redeploying the fragment to change origins |
57
+
|**Phase 2**|`DynamicCorsNamedValues` fragment | JSON string in a Named Value | Updateable in the portal; **4,096-char limit** per Named Value |
58
+
|**Phase 3**|`DynamicCorsCached` fragment + admin API | APIM internal cache (single entry) | No size limit; updated via admin API; fail-closed when cache is empty; can swap to external Redis |
59
+
|**Phase 4**|`DynamicCorsCachedPerApi` fragment + admin API | APIM internal cache (per-API entry) | Per-API cache isolation; smaller cache reads; update one API without touching others |
| Complexity | Low | Low | Low | Medium | Medium |
71
76
72
77
**Legend:**`+` = advantage, `-` = limitation, `n/a` = not applicable to this approach.
73
78
74
79
-**Baseline** is the simplest starting point but cannot differentiate origins per API.
75
80
-**Phase 1** adds per-API control with zero infrastructure overhead, ideal for a small, stable set of origins.
76
81
-**Phase 2** removes the need to redeploy fragments when origins change, but is constrained by the 4,096-character Named Value limit.
77
82
-**Phase 3** lifts all size limits, enables runtime updates via an admin API, and adopts a fail-closed posture. The trade-off is the additional admin API surface and the requirement to initialise the cache after an APIM restart or scale-out.
83
+
-**Phase 4** builds on Phase 3 by storing each API's origins in a separate cache entry (`corsOriginMapping-{apiId}`). This means each request reads only its own API's origin array (smaller payload), and updating one API's origins does not require reloading the entire mapping. The trade-off is the same as Phase 3 plus the need to load each API's cache entry individually.
0 commit comments