├── media
└── vscode-snippets
│ ├── apim-vscode-snippets-1.png
│ ├── apim-vscode-snippets-2.png
│ └── apim-vscode-snippets-3.png
├── examples
├── oauth-proxy
│ ├── oauth-proxy-token-endpoint-fragment.xml
│ ├── oauth-proxy-validate-token-fragment.xml
│ ├── oauth-proxy-construct-authorization-redirect-fragment.xml
│ ├── oauth-proxy-slide-session-fragment.xml
│ ├── oauth-proxy-sign-out.xml
│ ├── oauth-proxy-sign-in.xml
│ ├── readme.md
│ ├── oauth-proxy-session-fragment.xml
│ └── oauth-proxy-callback.xml
├── List all inbound headers.policy.xml
├── Random load balancer simpler.policy.xml
├── Forward gateway hostname to backend for generating correct urls in responses.policy.xml
├── Set cache duration using response cache control header.policy.xml
├── Encrypt data using expressions.policy.xml
├── Send request context information to the backend service.policy.xml
├── Mask async calls as synchronous.policy.xml
├── Decrypt AES Data using policy expressions.xml
├── Use custom error messages for jwt-validate policy with on-error handler.policy.xml
├── Return HTTP 405 if the HTTP Method of the request is not defined.xml
├── Route requests to regional backend instances.xml
├── Filter response content based on product name.policy.xml
├── Authenticate using Managed Identity to access Service Bus.xml
├── Random load balancer.policy.xml
├── Route requests based on size.policy.xml
├── Add correlation id to inbound request.policy.xml
├── Authenticate using Managed Identity to access Storage Account.xml
├── Perform basic authentication.policy.xml
├── Parse a JWT token using expressions.xml
├── Pre-authorize requests based on HTTP method with validate-jwt.policy.xml
├── Loopback request for service at same API Management service.xml
├── Authenticate using Managed Identity to access Event Hub.xml
├── Get OAuth2 access token from AAD and forward it to the backend.policy.xml
├── Log errors to Stackify.policy.xml
├── Extract value from XML.xml
├── Call out to an HTTP endpoint and cache the response.policy.xml
├── Get X-CSRF token from SAP gateway using send request.policy.xml
├── Backend OAuth2 Authentication With Cache.policy.xml
├── DELETE a from to blobStorage account.xml
├── Authorize requests using external authorizer.policy.xml
├── Back-end API redundancy.policy.xml
├── Query CosmosDB.policy.xml
├── Look up Key Vault certificate using Managed Service Identity and call backend.policy.xml
├── Look up Key Vault secret using Managed Service Identity.policy.xml
├── Trigger Azure Data Factory Pipeline.policy.xml
├── Handle Power Query access request to custom API.policy.xml
├── Trigger Azure Data Factory Pipeline With Parameters.policy.xml
├── Create HMAC SHA256-Signed JWT.policy.xml
├── Extracting multiple values from xml documents.policy.xml
├── Generate Azure Relay Token.policy.xml
├── GET a file from blobStorage account.xml
├── Filter on IP Address when using Application Gateway.policy.xml
├── Replay request on error.policy.xml
├── PUT a file to blobStorage account.xml
├── Forward Azure Event Grid Event.xml
├── Generate Shared Access Signature and forward request to Azure storage.policy.xml
├── Get OAuth2 access token from AAD using client id and certificate using key vault manage identity.xml
├── Return a blob URL signed with a user delegation SAS token.xml
└── Request OAuth2 access token from SuccessFactors using AAD JWT token.xml
├── LICENSE
├── README.md
├── SECURITY.md
└── policy-expressions
└── README.md
/media/vscode-snippets/apim-vscode-snippets-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure/api-management-policy-snippets/HEAD/media/vscode-snippets/apim-vscode-snippets-1.png
--------------------------------------------------------------------------------
/media/vscode-snippets/apim-vscode-snippets-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure/api-management-policy-snippets/HEAD/media/vscode-snippets/apim-vscode-snippets-2.png
--------------------------------------------------------------------------------
/media/vscode-snippets/apim-vscode-snippets-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Azure/api-management-policy-snippets/HEAD/media/vscode-snippets/apim-vscode-snippets-3.png
--------------------------------------------------------------------------------
/examples/oauth-proxy/oauth-proxy-token-endpoint-fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/examples/oauth-proxy/oauth-proxy-validate-token-fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 | {{ClientId}}
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/examples/List all inbound headers.policy.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {% for header in context.Request.Headers %}
11 | {{- header.Key }}: {{ header.Value }}
12 | {% endfor %}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/examples/Random load balancer simpler.policy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/examples/Forward gateway hostname to backend for generating correct urls in responses.policy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | @("proto=" + context.Request.OriginalUrl.Scheme + ";host=" + context.Request.OriginalUrl.Host + ";")
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/examples/Set cache duration using response cache control header.policy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | \d+)").Groups["maxAge"]?.Value;
18 | return (!string.IsNullOrEmpty(maxAge))?int.Parse(maxAge):300;
19 | }"
20 | />
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/examples/Encrypt data using expressions.policy.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/examples/Send request context information to the backend service.policy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | @(context.Product.Name)
11 |
12 |
13 |
14 |
15 | @(context.User.Id)
16 | @(context.Deployment.Region)
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation. All rights reserved.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
22 |
--------------------------------------------------------------------------------
/examples/oauth-proxy/oauth-proxy-construct-authorization-redirect-fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/examples/Mask async calls as synchronous.policy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | @(context.Response.Headers["location"][0])
19 | GET
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/examples/Decrypt AES Data using policy expressions.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
17 |
18 |
19 | @{
20 | string inBody = (string)context.Variables["plainText"];
21 | return inBody;
22 | }
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/examples/Use custom error messages for jwt-validate policy with on-error handler.policy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{base64-encoded-hashing-secret}}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 | Unauthorized. Access token is missing or invalid.
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/examples/Return HTTP 405 if the HTTP Method of the request is not defined.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | @{
22 | return new JObject(
23 | new JProperty("status", "HTTP 405"),
24 | new JProperty("message", "Method not allowed")
25 | ).ToString();
26 | }
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/examples/Route requests to regional backend instances.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/examples/Filter response content based on product name.policy.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | @{
22 | var response = context.Response.Body.As();
23 | foreach (var key in new [] {"current", "minutely", "hourly", "daily", "alerts"}) {
24 | response.Property (key).Remove ();
25 | }
26 | return response.ToString();
27 | }
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/examples/oauth-proxy/oauth-proxy-slide-session-fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
21 |
22 |
23 |
24 | @($"{{CookiePrefix}}={(string)context.Variables["encryptedCookie"]}; SameSite=Lax; secure; path=/; expires={DateTimeOffset.FromUnixTimeMilliseconds((long)context.Variables["cookie-expiry"]).ToString("R")}; Secure; HttpOnly" )
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/examples/Authenticate using Managed Identity to access Service Bus.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | @((string)context.Variables["msi-access-token"])
16 |
17 | {
18 | "Body": "APIM sending request using AAD Token",
19 | "BrokerProperties":{"Trusted Service":"APIM"},
20 | "UserProperties":{"Priority":"Medium","Customer":"Contoso"}
21 | }
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/examples/Random load balancer.policy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | @{return Guid.NewGuid().ToString();}
20 |
21 | A gateway-related error occurred while processing the request.
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/examples/Route requests based on size.policy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/examples/Add correlation id to inbound request.policy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | @{
15 | var guidBinary = new byte[16];
16 | Array.Copy(Guid.NewGuid().ToByteArray(), 0, guidBinary, 0, 10);
17 | long time = DateTime.Now.Ticks;
18 | byte[] bytes = new byte[6];
19 | unchecked
20 | {
21 | bytes[5] = (byte)(time >> 40);
22 | bytes[4] = (byte)(time >> 32);
23 | bytes[3] = (byte)(time >> 24);
24 | bytes[2] = (byte)(time >> 16);
25 | bytes[1] = (byte)(time >> 8);
26 | bytes[0] = (byte)(time);
27 | }
28 | Array.Copy(bytes, 0, guidBinary, 10, 6);
29 | return new Guid(guidBinary).ToString();
30 | }
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/examples/Authenticate using Managed Identity to access Storage Account.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | https://{{storageaccountname}}.blob.core.windows.net/container/{{blobtoread}}
14 | GET
15 |
16 | 2019-07-07
17 |
18 |
19 |
20 |
21 |
22 | @($"{((IResponse)context.Variables["blobdata"]).Body.As() }")
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/examples/Perform basic authentication.policy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/examples/Parse a JWT token using expressions.xml:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
24 |
25 |
26 | {{signing-key}}
27 |
28 |
29 |
30 |
33 |
34 | element == "sendEmail");
38 | }">
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/examples/Pre-authorize requests based on HTTP method with validate-jwt.policy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | {{signing-key}}
14 |
15 |
16 |
17 | true
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | {{signing-key}}
26 |
27 |
28 |
29 | true
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | {{signing-key}}
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/examples/Loopback request for service at same API Management service.xml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 | https://localhost/{your API Management service endpoint url}
18 | GET
19 |
20 | application/json
21 |
22 |
23 | {your API Management service domain}
24 |
25 |
26 | @($"{(string)context.Variables["subscriptionKey"]}")
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/examples/Authenticate using Managed Identity to access Event Hub.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | @(String.Concat("Bearer ",(string)context.Variables["msi-access-token"]))
21 |
22 | { "Event":"apim-using -aad token", "TrustedService":"AAD" }
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/examples/Get OAuth2 access token from AAD and forward it to the backend.policy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | {{authorizationServer}}
17 | POST
18 |
19 | application/x-www-form-urlencoded
20 |
21 |
22 | @{
23 | return "client_id={{clientId}}&scope={{scope}}&client_secret={{clientSecret}}&grant_type=client_credentials";
24 |
25 | // For Azure AD v1, try return statement below
26 | // return "client_id={{clientId}}&resource={{scope}}&client_secret={{clientSecret}}&grant_type=client_credentials";
27 | }
28 |
29 |
30 |
31 |
32 |
33 | @("Bearer " + (String)((IResponse)context.Variables["bearerToken"]).Body.As()["access_token"])
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/examples/Log errors to Stackify.policy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | On Error
23 |
24 |
25 | https://api.stackify.com/Log/Save
26 | POST
27 |
28 | value
29 |
30 |
31 | V1
32 |
33 |
34 | {{stackify-api-key}}
35 |
36 |
37 | @{
38 | return new JObject(
39 | new JProperty("Environment","{{environment-name}}"),
40 | new JProperty("ServerName", context.Deployment.ServiceName),
41 | new JProperty("AppName", "{{app-name}}"),
42 | new JProperty("AppLoc", "/usr/local/stackify/stackify-agent"),
43 | new JProperty("Logger", "stackify-log-log4j12-1.0.12"),
44 | new JProperty("Platform", "java"),
45 | new JProperty("Msgs",
46 | new JArray(
47 | new JObject(
48 | new JProperty("Msg", context.LastError.Message),
49 | new JProperty("Th", "main"),
50 | new JProperty("EpochMs", (new DateTimeOffset(DateTime.Now)).ToUnixTimeSeconds() * 1000 ),
51 | new JProperty("Level", "error"),
52 | new JProperty("SrcMethod", context.LastError.Source)
53 | )))
54 | ).ToString();
55 | }
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/examples/Extract value from XML.xml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 | @{
16 | // The XML from which you want to extract information may come from various places.
17 | var xml = "ABCDEF123456789";
18 | //var xml = context.Request.Body.As(preserveContent: true); //read from request body while also preserving the body
19 | //var xml = context.Response.Body.As(preserveContent: true); //read from response body while also preserving the body
20 |
21 | var ret = "";
22 |
23 | using (XmlReader reader = XmlReader.Create(new StringReader(xml)))
24 | {
25 | reader.MoveToContent();
26 | reader.Read();
27 |
28 | while (!reader.EOF && ret == "")
29 | {
30 | // Read until we reach the node of interest, then extract the information into a return value, which will gracefully break out of the loop
31 | if (reader.Name == "SomeCustomNestedElement")
32 | {
33 | var el = XNode.ReadFrom(reader) as XElement;
34 | ret = el == null ? "" : el.Value;
35 | } else {
36 | reader.Read();
37 | }
38 | }
39 | }
40 |
41 | return ret;
42 | }
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Azure API Management Policy Snippets
2 |
3 | # Policy Toolkit
4 |
5 | [Azure API Management policy toolkit](https://github.com/Azure/azure-api-management-policy-toolkit) is a set of libraries and tools for authoring policy documents for Azure API Management. The toolkit was designed to help create and test policy documents with complex expressions.
6 |
7 | # Examples
8 |
9 | The `examples/` folder contains policy examples contributed by the product team and the user community. The samples are meant to be re-used verbatim, provide inspiration or serve as learning aids. Some of them are parameterized using [Named Values](https://docs.microsoft.com/en-us/azure/api-management/api-management-howto-properties) (formerly known as Properties), which look like this: `{{some-value}}`. When using parametrized samples, you will have to either define relevant Named Values or replace them with values in place.
10 |
11 | # Policy expressions cheat-sheet
12 |
13 | The `policy-expressions` folder contains a [cheat-sheet](policy-expressions/README.md) with common policy expressions that are often used when authoring Azure API Management policies.
14 |
15 | # Visual Studio Code snippets
16 |
17 | The `vscode-snippets/` folder contains user snippets for Visual Studio Code. User snippets are helpful for streamlining workflow and simplifying document editing with autocomplete and easy navigation. Please, refer to the [Visual Studio Code documentation](https://code.visualstudio.com/docs/editor/userdefinedsnippets) on how to use them.
18 |
19 | 
20 |
21 | 
22 |
23 | 
24 |
25 | # Helpful Links
26 |
27 | - [Policies Reference](https://docs.microsoft.com/en-us/azure/api-management/api-management-policies)
28 | - [Policy Expressions](https://docs.microsoft.com/en-us/azure/api-management/api-management-policy-expressions)
29 | - [Handling Errors in Policies](https://docs.microsoft.com/en-us/azure/api-management/api-management-error-handling-policies)
30 | - [Policy Toolkit](https://github.com/Azure/azure-api-management-policy-toolkit)
31 | - [APIM Samples](https://aka.ms/apim/samples)
32 |
33 | To learn about Azure API Management go [here](https://azure.microsoft.com/en-us/services/api-management/).
34 |
35 | # Contributing
36 |
37 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
38 |
--------------------------------------------------------------------------------
/examples/Call out to an HTTP endpoint and cache the response.policy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | @{
22 | var code = context.Request.MatchedParameters.GetValueOrDefault("place");
23 | var key = "{{google-geo-api-key}}";
24 | return $"https://maps.googleapis.com/maps/api/geocode/json?address={code}&key={key}";
25 | }
26 |
27 | GET
28 |
29 |
30 |
31 |
32 | ()["results"][0]["geometry"]["location"].ToString())"/>
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
6 |
7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
14 |
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
16 |
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
18 |
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
40 |
41 |
42 |
--------------------------------------------------------------------------------
/examples/Get X-CSRF token from SAP gateway using send request.policy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | @(context.Request.Url.ToString())
15 | HEAD
16 |
17 | Fetch
18 |
19 |
20 |
21 | @(context.Request.Headers.GetValueOrDefault("Authorization"))
22 |
23 |
24 |
25 |
26 |
27 |
28 | @(((IResponse)context.Variables["SAPCSRFToken"]).Headers.GetValueOrDefault("x-csrf-token"))
29 |
30 |
31 | @{
32 | string rawcookie = ((IResponse)context.Variables["SAPCSRFToken"]).Headers.GetValueOrDefault("Set-Cookie");
33 | string[] cookies = rawcookie.Split(';');
34 | /* new session sends a XSRF cookie */
35 | string xsrftoken = cookies.FirstOrDefault( ss => ss.Contains("sap-XSRF"));
36 | /* existing sessions sends a SessionID. No other cases anticipated at this point. Please create a GitHub Pull-Request if you encounter uncovered settings. */
37 | if(xsrftoken == null){
38 | xsrftoken = cookies.FirstOrDefault( ss => ss.Contains("SAP_SESSIONID"));
39 | }
40 |
41 | return xsrftoken.Split(',')[1];}
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/examples/Backend OAuth2 Authentication With Cache.policy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | {{authorizationServer}}
21 | POST
22 |
23 | application/x-www-form-urlencoded
24 |
25 | @{
26 | return "client_id={{clientId}}&scope={{scope}}&client_secret={{clientSecret}}&grant_type=client_credentials";
27 | }
28 |
29 | ())" />
30 |
31 |
32 |
33 |
34 |
35 |
36 | @("Bearer " + (string)context.Variables["bearerToken"])
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/examples/DELETE a from to blobStorage account.xml:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | @{ return "https://{{StorageAccount.Name}}.blob.core.windows.net/" + (string)context.Request.MatchedParameters("blobContainer") + "/" + (string)context.Request.MatchedParameters("fileNameWithExtension",""); }
27 | DELETE
28 |
29 | {{StorageAccount.Name}}.blob.core.windows.net
30 |
31 |
32 | {{Gateway.Name}}
33 |
34 |
35 | 2019-12-12
36 |
37 |
38 |
39 | @("Bearer " + (string)context.Variables["msi-access-token"])
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/examples/Authorize requests using external authorizer.policy.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | @("Bearer realm="+context.Request.OriginalUrl.Host)
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
34 |
35 |
36 |
37 | {{authorizer-url}}
38 | GET
39 |
40 | @(context.Request.Headers.GetValueOrDefault("Authorization"))
41 |
42 |
43 |
44 | ()["status"].ToString())"/>
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/examples/Back-end API redundancy.policy.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/examples/Query CosmosDB.policy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | https://{database-account}.documents.azure.com/dbs/{db}/colls/{coll}/docs
14 | POST
15 |
16 | @{
17 | var verb = "post";
18 | var resourceType = "docs";
19 | var resourceLink = "";
20 | var key = "";
21 | var keyType = "master";
22 | var tokenVersion = "1.0";
23 | var date = context.Variables.GetValueOrDefault("requestDateString");
24 |
25 | var hmacSha256 = new System.Security.Cryptography.HMACSHA256 { Key = Convert.FromBase64String(key) };
26 |
27 | verb = verb ?? "";
28 | resourceType = resourceType ?? "";
29 | resourceLink = resourceLink ?? "";
30 |
31 | string payLoad = string.Format("{0}\n{1}\n{2}\n{3}\n{4}\n",
32 | verb.ToLowerInvariant(),
33 | resourceType.ToLowerInvariant(),
34 | resourceLink,
35 | date.ToLowerInvariant(),
36 | ""
37 | );
38 |
39 | byte[] hashPayLoad = hmacSha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payLoad));
40 | string signature = Convert.ToBase64String(hashPayLoad);
41 |
42 | return System.Uri.EscapeDataString(String.Format("type={0}&ver={1}&sig={2}",
43 | keyType,
44 | tokenVersion,
45 | signature));
46 | }
47 |
48 |
49 | application/query+json
50 |
51 |
52 | True
53 |
54 |
55 | @(context.Variables.GetValueOrDefault("requestDateString"))
56 |
57 |
58 | 2017-02-22
59 |
60 |
61 | true
62 |
63 |
64 | @("{\"query\": \"SELECT * FROM c WHERE c.id = @id\", " +
65 | "\"parameters\": [{ \"name\": \"@id\", \"value\": \"" + context.User.Id + "\"}]}")
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/examples/Look up Key Vault certificate using Managed Service Identity and call backend.policy.xml:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | https://msikvtest.vault.azure.net/secrets/mycert/?api-version=2016-10-01
26 | GET
27 |
28 |
29 |
30 | ()["value"].ToString())" />
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/examples/Look up Key Vault secret using Managed Service Identity.policy.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | https://msikvtest.vault.azure.net/secrets/mysecret/?api-version=7.0
25 | GET
26 |
27 |
28 |
29 |
30 | ())" />
31 |
32 |
33 |
34 |
35 |
36 |
37 | application/json
38 |
39 | @((string)context.Variables["keyvaultsecretResponse"])
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/examples/Trigger Azure Data Factory Pipeline.policy.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
37 |
38 |
39 |
40 |
41 | @(String.Concat("Bearer ", ((string)context.Variables.GetValueOrDefault("managed-identity-token"))))
42 |
43 |
44 |
45 |
46 |
47 | application/json
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/examples/Handle Power Query access request to custom API.policy.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | Bearer authorization_uri=https://login.microsoftonline.com/{{AADTenantId}}/oauth2/v2.0/authorize?response_type=code%26client_id=a672d62c-fc7b-4e81-a576-e60dc46e951d
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | https://your-custom-apim-domain
34 |
35 |
36 | https://sts.windows.net/{{AADTenantId}}/
37 |
38 |
39 |
40 | user_impersonation
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/examples/Trigger Azure Data Factory Pipeline With Parameters.policy.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
37 |
38 |
39 |
40 |
41 | @(String.Concat("Bearer ", ((string)context.Variables.GetValueOrDefault("managed-identity-token"))))
42 |
43 |
44 |
45 |
46 |
47 | application/json
48 |
49 |
50 | {
51 | "UserEmailAddress": "{{context.Request.MatchedParameters["emailAddress"]}}"
52 | }
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/examples/Create HMAC SHA256-Signed JWT.policy.xml:
--------------------------------------------------------------------------------
1 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | @{
37 | // 1) Construct the Base64Url-encoded header
38 | var header = new { typ = "JWT", alg = "HS256" };
39 | var jwtHeaderBase64UrlEncoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(header))).Replace("/", "_").Replace("+", "-"). Replace("=", "");
40 | // As the header is a constant, you may use this equivalent Base64Url-encoded string instead to save the repetitive computation above.
41 | // var jwtHeaderBase64UrlEncoded = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9";
42 |
43 | // 2) Construct the Base64Url-encoded payload
44 | var exp = new DateTimeOffset(DateTime.Now.AddMinutes(10)).ToUnixTimeSeconds(); // sets the expiration of the token to be 10 minutes from now
45 | var username = "john_doe";
46 | var payload = new { exp, username };
47 | var jwtPayloadBase64UrlEncoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload))).Replace("/", "_").Replace("+", "-"). Replace("=", "");
48 |
49 | // 3) Construct the Base64Url-encoded signature
50 | var signature = new HMACSHA256(Encoding.UTF8.GetBytes("{{hashing-secret}}")).ComputeHash(Encoding.UTF8.GetBytes($"{jwtHeaderBase64UrlEncoded}.{jwtPayloadBase64UrlEncoded}"));
51 | var jwtSignatureBase64UrlEncoded = Convert.ToBase64String(signature).Replace("/", "_").Replace("+", "-"). Replace("=", "");
52 |
53 | // 4) Return the HMAC SHA256-signed JWT as the value for the Authorization header
54 | return $"Bearer {jwtHeaderBase64UrlEncoded}.{jwtPayloadBase64UrlEncoded}.{jwtSignatureBase64UrlEncoded}";
55 | }
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/examples/Extracting multiple values from xml documents.policy.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
20 |
21 | {{my-send-request}}
22 | GET
23 |
24 | text/xml
25 |
26 |
27 |
28 |
29 | ());
32 |
33 | var obj = new JObject();
34 | obj["conditionValue"] = doc.Descendants().Where(r=>r.Name.LocalName == "conditionValue").First().Value;
35 | obj["value1"] = doc.Descendants().Where(r=>r.Name.LocalName == "value1").First().Value;
36 | obj["value3"] = doc.Descendants().Where(r=>r.Name.LocalName == "value3").First().Value;
37 |
38 | return obj;
39 |
40 | }" />
41 |
51 |
52 |
53 |
54 |
55 |
56 | Condition was met
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | text/xml
67 |
68 |
69 | {%- assign vars = context.Variables["jsonResponse"] -%}
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/examples/Generate Azure Relay Token.policy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | @((string)context.Variables["relaytoken"])
44 |
45 |
46 |
47 | application/xml
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | application/json
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/examples/GET a file from blobStorage account.xml:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | @{ return "https://{{StorageAccount.Name}}.blob.core.windows.net/" + (string)context.Request.MatchedParameters("blobContainer") + "/" + (string)context.Request.MatchedParameters("fileNameWithExtension",""); }
27 | GET
28 |
29 | {{StorageAccount.Name}}.blob.core.windows.net
30 |
31 |
32 | {{Gateway.Name}}
33 |
34 |
35 | BlockBlob
36 |
37 |
38 | @{ return Guid.NewGuid().ToString(); }
39 |
40 |
41 | 2019-12-12
42 |
43 |
44 | */*
45 |
46 |
47 |
48 | @("Bearer " + (string)context.Variables["msi-access-token"])
49 |
50 |
51 |
52 |
53 |
54 |
55 | @(((IResponse)context.Variables["result"]).Headers.GetValueOrDefault("x-ms-meta-{MyMetadataName1}",""))
56 |
57 |
58 | @(((IResponse)context.Variables["result"]).Headers.GetValueOrDefault("x-ms-meta-{MyMetadataName2}",""))
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/policy-expressions/README.md:
--------------------------------------------------------------------------------
1 | # Common policy expressions
2 | This cheat-sheet contains common policy expressions that are often used when authoring Azure API Management policies.
3 |
4 | ## Interact with HTTP headers
5 |
6 | **Get HTTP header**
7 | ```c#
8 | context.Request.Headers.GetValueOrDefault("header-name","optional-default-value")
9 | ```
10 | **Check HTTP header existence**
11 | ```c#
12 | context.Request.Headers.ContainsKey("header-name") == true
13 | ```
14 | **Check if HTTP header has expected value**
15 | ```c#
16 | context.Request.Headers.GetValueOrDefault("header-name", "").Equals("expected-header-value", StringComparison.OrdinalIgnoreCase)
17 | ```
18 | ## Interact with URI parameters
19 |
20 | **Get URI parameter**
21 | ```c#
22 | context.Request.MatchedParameters.GetValueOrDefault("parameter-name","optional-default-value")
23 | ```
24 | **Check URI parameter existence**
25 | ```c#
26 | context.Request.MatchedParameters.ContainsKey("parameter-name") == true
27 | ```
28 | **Check if URI parameter has expected value**
29 | ```c#
30 | context.Request.MatchedParameters.GetValueOrDefault("parameter-name", "").Equals("expected-value", StringComparison.OrdinalIgnoreCase) == true
31 | ```
32 | ## Interact with query string parameters
33 |
34 | **Get query string parameter**
35 | ```c#
36 | context.Request.Url.Query.GetValueOrDefault("parameter-name", "optional-default-value")
37 | ```
38 | **Check query string parameter existence**
39 | ```c#
40 | context.Request.Url.Query.ContainsKey("parameter-name") == true
41 | ```
42 | **Check if query string parameter has expected value**
43 | ```c#
44 | context.Request.Url.Query.GetValueOrDefault("parameter-name", "").Equals("expected-value", StringComparison.OrdinalIgnoreCase) == true
45 | ```
46 | ## Interact with policy variables
47 |
48 | **Get policy variable** *(assuming type string)*
49 | ```c#
50 | context.Variables.GetValueOrDefault("variable-name","optional-default-value")
51 | ```
52 | **Check policy variable existence**
53 | ```c#
54 | context.Variables.ContainsKey("variable-name") == true
55 | ```
56 | **Check if policy variable has expected value** *(assuming type string)*
57 | ```c#
58 | context.Variables.GetValueOrDefault("variable-name","").Equals("expected-value", StringComparison.OrdinalIgnoreCase)
59 | ```
60 | ## Interact with JSON bodies
61 |
62 | **Get value from JSON body**
63 | ```c#
64 | (string)context.Request.Body.As(preserveContent: true).SelectToken("root.child jsonpath")
65 | ```
66 | **Get value from JSON response variable**
67 | ```c#
68 | (string)((IResponse)context.Variables["response-variable-name"]).Body.As().SelectToken("root.child jsonpath")
69 | ```
70 | **Add property to JSON body**
71 | ```c#
72 | JObject body = context.Request.Body.As();
73 | body.Add(new JProperty("property-name", "property-value"));
74 | return body.ToString();
75 | ```
76 | ## Interact with JSON Web Tokens
77 |
78 | **Read claim from bearer token**
79 | ```c#
80 | context.Request.Headers.GetValueOrDefault("Authorization")?.Split(' ')?[1].AsJwt()?.Claims["claim-name"].FirstOrDefault()
81 | ```
82 |
83 | ## Interact with client certificates
84 |
85 | **Check client certificate existence**
86 | ```c#
87 | context.Request.Certificate != null
88 | ```
89 | **Check if client certificate is valid, including a certificate revocation check**
90 | ```c#
91 | context.Request.Certificate.Verify() == true
92 | ```
93 | **Check if client certificate is valid, excluding a certificate revocation check**
94 | ```c#
95 | context.Request.Certificate.VerifyNoRevocation() == true
96 | ```
97 | **Check if client certificate issuer has expected value**
98 | ```c#
99 | context.Request.Certificate.Issuer == "trusted-issuer"
100 | ```
101 | **Check if client certificate subject has expected value**
102 | ```c#
103 | context.Request.Certificate.SubjectName.Name == "expected-subject-name"
104 | ```
105 | **Check if client certificate thumbprint has expected value**
106 | ```c#
107 | context.Request.Certificate.Thumbprint == "EXPECTED-THUMBPRINT-IN-UPPER-CASE"
108 | ```
109 | **Check if client certificate is uploaded in API Management, based on thumbprint**
110 | ```c#
111 | context.Deployment.Certificates.Any(c => c.Value.Thumbprint == context.Request.Certificate.Thumbprint) == true
112 | ```
--------------------------------------------------------------------------------
/examples/oauth-proxy/oauth-proxy-sign-out.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | cookie.Trim().Split('=')).SingleOrDefault(cookie => cookie[0] == "oidcsession")?[1] ?? string.Empty : string.Empty)" />
5 |
6 |
7 |
8 |
9 |
10 |
11 | @($"/oauth/signin?redirect={Uri.EscapeDataString(context.Request.OriginalUrl.ToString())}")
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | @($"{{CookiePrefix}}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT")
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | @($"{{CookiePrefix}}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT")
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | @((string)context.Variables["redirect"])
56 |
57 |
58 | @($"{{CookiePrefix}}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT")
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/examples/Filter on IP Address when using Application Gateway.policy.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | @{
17 | int HostToNetworkOrder(int host)
18 | {
19 | return (((int)HostToNetworkOrderShort((short)host) & 0xFFFF) << 16)
20 | | ((int)HostToNetworkOrderShort((short)(host >> 16)) & 0xFFFF);
21 | }
22 | short HostToNetworkOrderShort(short host)
23 | {
24 | return (short)((((int)host & 0xFF) << 8) | (int)((host >> 8) & 0xFF));
25 | }
26 |
27 | string ipAddress = context.Request.Headers.GetValueOrDefault("x-forwarded-for","");
28 | if (!string.IsNullOrEmpty(ipAddress))
29 | {
30 | string[] tokens = ipAddress.Split(':');
31 | if(tokens.Length == 2)
32 | { ipAddress = tokens[0]; }
33 | //Place IP Ranges into this list in CIDR notation (e.g. "0.0.0.0/0") and separate with commas
34 | List cidrList = new List(){
35 | "10.0.0.0/8",
36 | "172.16.0.0/12",
37 | "192.168.0.0/16"
38 | };
39 | foreach (string cidrAddress in cidrList)
40 | {
41 | string[] cidrParts = cidrAddress.Split('/');
42 | string[] inputIPParts = ipAddress.Split('.');
43 | string[] cidrIPArray = cidrParts[0].Split('.');
44 |
45 | if (inputIPParts.Length == 4 && cidrIPArray.Length == 4)
46 | {
47 | byte[] inputIPBytes = new byte[] {Convert.ToByte(int.Parse(inputIPParts[0])),
48 | Convert.ToByte(int.Parse(inputIPParts[1])),
49 | Convert.ToByte(int.Parse(inputIPParts[2])),
50 | Convert.ToByte(int.Parse(inputIPParts[3])), };
51 | byte[] cidrIPBytes = new byte[] {Convert.ToByte(int.Parse(cidrIPArray[0])),
52 | Convert.ToByte(int.Parse(cidrIPArray[1])),
53 | Convert.ToByte(int.Parse(cidrIPArray[2])),
54 | Convert.ToByte(int.Parse(cidrIPArray[3])), };
55 |
56 | int cidrAddr = BitConverter.ToInt32(inputIPBytes,0);
57 | int ipAddr = BitConverter.ToInt32(cidrIPBytes,0);
58 |
59 | var host = int.Parse(cidrParts[1]);
60 | host = -1 << (32-host);
61 | var mask = HostToNetworkOrder(host);
62 |
63 | if (((ipAddr & mask) == (cidrAddr & mask)))
64 | {
65 | return "{{ipValidated}}";
66 | }
67 | }
68 | }
69 | }
70 | return ipAddress; }
71 |
72 |
73 | 10.33.215.173
74 | 10.33.215.175
75 | {{ipValidated}}
76 |
77 |
78 |
79 | @{
80 | return context.Variables.GetValueOrDefault("originalXForwardedForValue");
81 | }
82 |
83 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/examples/Replay request on error.policy.xml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
34 |
38 |
39 | @(context.Variables.GetValueOrDefault("replay-url"))
40 |
44 |
45 | true
46 |
47 |
48 |
51 |
52 |
53 |
54 | true
55 |
56 |
57 | @(context.Variables.GetValueOrDefault("original-error-source"))
58 |
59 |
60 | @(context.Variables.GetValueOrDefault("original-error-reason"))
61 |
62 |
63 |
64 |
65 |
66 |
67 | "false"
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/examples/PUT a file to blobStorage account.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | @{ return "https://{{StorageAccount.Name}}.blob.core.windows.net/" + (string)context.Request.MatchedParameters("blobContainer") + "/" + (string)context.Request.MatchedParameters("fileNameWithExtension",""); }
28 | PUT
29 |
30 | {{StorageAccount.Name}}.blob.core.windows.net
31 |
32 |
33 | {{Gateway.Name}}
34 |
35 |
36 | BlockBlob
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | @((string)context.Request.Headers.GetValueOrDefault("x-ms-meta-{MyMetadataName1}"))
53 |
54 |
55 | @((string)context.Request.Headers.GetValueOrDefault("x-ms-meta-{MyMetadataName2}"))
56 |
57 |
58 | @{ return Guid.NewGuid().ToString(); }
59 |
60 |
61 | 2019-12-12
62 |
63 |
64 | application/json
65 |
66 |
67 |
68 | @("Bearer " + (string)context.Variables["msi-access-token"])
69 |
70 |
71 | @(context.Request.Body.As())
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/examples/Forward Azure Event Grid Event.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | application/json
21 |
22 | {"error":"We only support one event"}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | application/json
33 |
34 | @{
35 | var validationResponse = new JObject(new JProperty("validationResponse",context.Variables.GetValueOrDefault("Event")["data"]["validationCode"].ToString()));
36 | return validationResponse.ToString();
37 | }
38 |
39 |
40 |
41 |
42 | application/json
43 |
44 |
45 | @(context.Variables.GetValueOrDefault("Event")["id"].ToString())
46 |
47 |
48 | @(context.Variables.GetValueOrDefault("Event")["subject"].ToString())
49 |
50 |
51 | @(context.Variables.GetValueOrDefault("Event")["eventType"].ToString())
52 |
53 |
54 | @(context.Variables.GetValueOrDefault("Event")["eventTime"].ToString())
55 |
56 |
57 | @(context.Variables.GetValueOrDefault("Event")["dataVersion"].ToString())
58 |
59 |
60 | @(context.Variables.GetValueOrDefault("Event")["metadataVersion"].ToString())
61 |
62 |
63 | @(context.Variables.GetValueOrDefault("Event")["topic"].ToString())
64 |
65 | @(context.Variables.GetValueOrDefault("Event")["data"].ToString())
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/examples/oauth-proxy/oauth-proxy-sign-in.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Missing redirect parameter
10 |
11 |
12 |
13 |
14 |
16 |
17 |
18 |
19 | Invalid redirect
20 |
21 |
22 |
24 |
25 |
26 |
27 | Invalid redirect
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | @((string)context.Variables["idpRedirect"])
70 |
71 |
72 | @($"{{CookiePrefix}}-{(string)context.Variables["state"]}={(string)context.Variables["cookie"]}; Secure; SameSite=None; Path=/; Expires={DateTimeOffset.Now.AddSeconds(300).ToString("R")}; Secure; HttpOnly")
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/examples/Generate Shared Access Signature and forward request to Azure storage.policy.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
44 |
60 |
66 |
75 |
76 |
77 | @((string)context.Variables["contentType"])
78 |
79 |
80 | application/json;odata=nometadata
81 |
82 |
83 | UTF-8
84 |
85 |
86 | @((string)context.Variables["x-ms-date"])
87 |
88 |
89 | @((string)context.Variables["x-ms-version"])
90 |
91 |
92 |
93 |
94 | 3.0
95 |
96 |
97 | 1.0;NetFx
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/examples/Get OAuth2 access token from AAD using client id and certificate using key vault manage identity.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | https://{{KeyVaultName}}.vault.azure.net/secrets/{{CertName}}/?api-version=2016-10-01
28 | GET
29 |
30 |
31 |
32 |
33 | ()["value"].ToString())" />
34 |
35 |
36 |
37 |
38 |
39 |
40 |
52 | ()
53 | {
54 | { "aud", aud },
55 | { "exp", exp.ToString() },
56 | { "iss", clientID },
57 | { "jti", Guid.NewGuid().ToString() },
58 | { "nbf", nbf.ToString() },
59 | { "sub", clientID }
60 | };
61 |
62 |
63 | X509Certificate2 certificate = new X509Certificate2(Convert.FromBase64String((string)context.Variables["keyVaultCertBase64"]), (string)null);
64 | string signedClientAssertion = "";
65 | RSACng rsa = certificate.GetRSAPrivateKey() as RSACng;
66 |
67 | //Encoding variable
68 | char Base64PadCharacter = '=';
69 | char Base64Character62 = '+';
70 | char Base64Character63 = '/';
71 | char Base64UrlCharacter62 = '-';
72 | char Base64UrlCharacter63 = '_';
73 |
74 | string certEncode = Convert.ToBase64String(certificate.GetCertHash()).Split(Base64PadCharacter)[0].Replace(Base64Character62, Base64UrlCharacter62).Replace(Base64Character63, Base64UrlCharacter63);
75 |
76 | //alg represents the desired signing algorithm, which is SHA-256 in this case
77 | //kid represents the certificate thumbprint
78 | var header = new Dictionary
79 | ()
80 | {
81 | { "alg", "RS256"},
82 | { "kid", certEncode }
83 | };
84 |
85 | string headerEncode = Convert.ToBase64String(Encoding.UTF8.GetBytes(JObject.FromObject(header).ToString())).Split(Base64PadCharacter)[0].Replace(Base64Character62, Base64UrlCharacter62).Replace(Base64Character63, Base64UrlCharacter63);
86 | string claimsEncode = Convert.ToBase64String(Encoding.UTF8.GetBytes(JObject.FromObject(claims).ToString())).Split(Base64PadCharacter)[0].Replace(Base64Character62, Base64UrlCharacter62).Replace(Base64Character63, Base64UrlCharacter63);
87 | string token = headerEncode + "." + claimsEncode;
88 | string signature = Convert.ToBase64String(rsa.SignData(Encoding.UTF8.GetBytes(token), HashAlgorithmName.SHA256, System.Security.Cryptography.RSASignaturePadding.Pkcs1)).Split(Base64PadCharacter)[0].Replace(Base64Character62, Base64UrlCharacter62).Replace(Base64Character63, Base64UrlCharacter63);
89 | signedClientAssertion = string.Concat(token, ".", signature);
90 | return signedClientAssertion;
91 | }" />
92 |
93 |
94 |
95 |
96 |
97 | @((string)context.Variables["authorizationServer"])
98 | POST
99 |
100 | application/x-www-form-urlencoded
101 |
102 |
103 | @{
104 | return "client_id={{clientId}}&resource={{scope}}&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=" + ((string)context.Variables["signedClientAssertion"]) + "&grant_type=client_credentials";
105 |
106 | // For identity providers other than Azure AD, try return statement below
107 | // return "client_id={{clientId}}&scope={{scope}}&client_secret={{clientSecret}}&grant_type=client_credentials";
108 | }
109 |
110 |
111 |
112 | ()["access_token"])" />
113 |
114 |
115 |
116 |
117 | @("Bearer " + (string)context.Variables["bearerToken"])
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/examples/Return a blob URL signed with a user delegation SAS token.xml:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | @((string)context.Variables["blobUri"])
31 | GET
32 |
33 | 2020-12-06
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | @("Missing permission to read blob " + (string)context.Variables["blobUri"] + ". Make sure the 'Storage Blob Data Reader' role is granted for APIM '"+ context.Deployment.ServiceName +"' either at the Storage Account or Container level.")
42 |
43 |
44 |
45 |
46 |
47 | @("Cannot find blob " + (string)context.Variables["blobUri"])
48 |
49 |
50 |
51 |
52 |
53 | @("https://"+((string)context.Request.MatchedParameters["storageAccountName"]) + ".blob.core.windows.net/?restype=service&comp=userdelegationkey")
54 | POST
55 |
56 | 2020-12-06
57 |
58 | @{
59 | return "" +
60 | "" + DateTime.UtcNow.ToString("u").Replace(" ", "T") + "" +
61 | "" + DateTime.UtcNow.AddSeconds((string)context.Request.MatchedParameters["ttlInSeconds"]).ToString("u").Replace(" ", "T") + "" +
62 | "";
63 | }
64 |
65 |
66 |
67 |
68 |
69 |
70 | @("Missing permission to generate SAS for blob " + (string)context.Variables["blobUri"] + ". Make sure the 'Storage Blob Delegator' role is granted for APIM '"+ context.Deployment.ServiceName +"' at the Storage Account level.")
71 |
72 |
73 |
74 |
75 | @{
76 | XmlDocument xml = new XmlDocument();
77 | string userDelegationKeyResponse = ((IResponse)context.Variables["userdelegationkey"]).Body.As();
78 | xml.LoadXml(userDelegationKeyResponse);
79 | var signedKeyObjectId = xml.DocumentElement.SelectSingleNode("/UserDelegationKey/SignedOid").InnerText;
80 | var signedKeyTenantId = xml.DocumentElement.SelectSingleNode("/UserDelegationKey/SignedTid").InnerText;
81 | var signedKeyStart = xml.DocumentElement.SelectSingleNode("/UserDelegationKey/SignedStart").InnerText;
82 | var signedKeyExpiry = xml.DocumentElement.SelectSingleNode("/UserDelegationKey/SignedExpiry").InnerText;
83 | var signedKeyService = xml.DocumentElement.SelectSingleNode("/UserDelegationKey/SignedService").InnerText;
84 | var signedKeyVersion = xml.DocumentElement.SelectSingleNode("/UserDelegationKey/SignedVersion").InnerText;
85 | var signedKey = xml.DocumentElement.SelectSingleNode("/UserDelegationKey/Value").InnerText;
86 |
87 | var signedPermissions = "r";
88 | var signedStart = "";
89 | var signedExpiry = DateTime.UtcNow.AddSeconds((string)context.Request.MatchedParameters["storageAccountName"]).ToString("yyyy-MM-ddTHH:mm:ssZ");
90 | var canonicalizedResource = $"/blob/{((string)context.Request.MatchedParameters["storageAccountName"])}/{((string)context.Request.MatchedParameters["containerName"])}/{((string)context.Request.MatchedParameters["blobName"])}";
91 | var signedAuthorizedUserObjectId = "";
92 | var signedUnauthorizedUserObjectId = "";
93 | var signedCorrelationId = "";
94 | var signedIP = "";
95 | var signedProtocol = "https";
96 | var signedVersion = "2020-12-06";
97 | var signedResource = "b";
98 | var signedSnapshotTime = "";
99 | var signedEncryptionScope = "";
100 | var rscc = "";
101 | var rscd = "";
102 | var rsce = "";
103 | var rscl = "";
104 | var rsct = "";
105 |
106 | var stringToSign = signedPermissions + "\n" +
107 | signedStart + "\n" +
108 | signedExpiry + "\n" +
109 | canonicalizedResource + "\n" +
110 | signedKeyObjectId + "\n" +
111 | signedKeyTenantId + "\n" +
112 | signedKeyStart + "\n" +
113 | signedKeyExpiry + "\n" +
114 | signedKeyService + "\n" +
115 | signedKeyVersion + "\n" +
116 | signedAuthorizedUserObjectId + "\n" +
117 | signedUnauthorizedUserObjectId + "\n" +
118 | signedCorrelationId + "\n" +
119 | signedIP + "\n" +
120 | signedProtocol + "\n" +
121 | signedVersion + "\n" +
122 | signedResource + "\n" +
123 | signedSnapshotTime + "\n" +
124 | signedEncryptionScope + "\n" +
125 | rscc + "\n" +
126 | rscd + "\n" +
127 | rsce + "\n" +
128 | rscl + "\n" +
129 | rsct;
130 | System.Security.Cryptography.HMACSHA256 hasher = new System.Security.Cryptography.HMACSHA256(Convert.FromBase64String(signedKey));
131 |
132 | var sig = System.Net.WebUtility.UrlEncode(Convert.ToBase64String(hasher.ComputeHash(System.Text.Encoding.UTF8.GetBytes(stringToSign))));
133 |
134 | var sas = $"?sv={signedVersion}&sp={signedPermissions}&se={signedExpiry}&sr={signedResource}&skoid={signedKeyObjectId}&sktid={signedKeyTenantId}&skt={signedKeyStart}&ske={signedKeyExpiry}&sks={signedKeyService}&skv={signedKeyVersion}&spr={signedProtocol}&sig={sig}";
135 |
136 | return (string)context.Variables["blobUri"] + sas;
137 | }
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
--------------------------------------------------------------------------------
/examples/oauth-proxy/readme.md:
--------------------------------------------------------------------------------
1 | # OAuth Proxy Azure API Management policies
2 |
3 | The policies in this folder provide support for an OAuth Proxy that works in a similar way to App Service Authentication.
4 |
5 | ## Setup
6 |
7 | ### Required Named Values
8 |
9 | | Named Value | Purpose |
10 | | -- | -- |
11 | | AdditionalScopes | Space separated string of other scopes to request delegated consent for |
12 | | ClientId | AAD ClientId Id representing the application you are signing in against |
13 | | ClientSecret | AAD Client Secret used to exchange codes for tokens |
14 | | CookiePrefix | The name we use for the cookie used to control the oauth-proxy |
15 | | CookieEncryptionKey | 1 or 2. Selects the key (CookieEncryptionKey**1** or CookieEncryptionKey**2**) used to protect newly issued cookies. This allows you to periodically rotate keys |
16 | | CookieEncryptionKey1 | A Base 64 Encoded string of a 32 random bytes array. Used by an AES 256 encryption algorithm to encrypt cookies |
17 | | CookieEncryptionKey2 | A Base 64 Encoded string of a 32 random bytes array. Used by an AES 256 encryption algorithm to encrypt cookies |
18 | | TokenEncryptionKey | 1 or 2. Selects the key (TokenEncryptionKey**1** or TokenEncryptionKey**2**) used to protect newly issued tokens. This allows you to periodically rotate keys |
19 | | TokenEncryptionKey1 | A Base 64 Encoded string of 32 random bytes. Used as the Key for an AES 256 encryption algorithm for encrypting tokens at rest |
20 | | TokenEncryptionKey2 | A Base 64 Encoded string of 32 random bytes. Used as the Key for an AES 256 encryption algorithm for encrypting tokens at rest |
21 | | SessionCookieExpirationInSeconds | How long to allow session cookies to stay active for |
22 | | RefreshTokenExpirationInSeconds | How long to cache refresh tokens for (a good guide would be how long your average user's session lasts for) |
23 |
24 | > You can generate the Base 64 random bytes in dotnet using ``` Convert.ToBase64String(RandomNumberGenerator.GetBytes()) ```, or in bash using ```openssl rand -base64 32```
25 |
26 | ### Required Named Values for Azure Active Directory
27 |
28 | | Named Value | Purpose |
29 | | -- | -- |
30 | | TenantId | AAD Tenant Id that owns the ClientId you want users to sign-in to |
31 |
32 |
33 | ## Fragments
34 | | Fragment File Name | Fragment Name | Purpose | How to use |
35 | | -- | -- | -- | -- |
36 | | [oauth-proxy-token-endpoint-fragment.xml](oauth-proxy-token-endpoint-fragment.xml) | oauth-proxy-token-endpoint-fragment | Identifies the token endpoint to obtains tokens from | You **must** place this fragment above the ```oauth-proxy-session-fragment``` as it sets a required variable used by other policies. |
37 | | [oauth-proxy-validate-token-fragment.xml](oauth-proxy-validate-token-fragment.xml) | oauth-proxy-validate-token-fragment | Custom token validation policy | Place it after the ```oauth-proxy-session-fragment``` to provide an additional JWT validation step on the access-token returned by your IdP. The reference implementation uses the [validate-azure-ad-token](https://learn.microsoft.com/en-us/azure/api-management/validate-azure-ad-token-policy) policy. For other IdPs use the [validate-jwt](https://learn.microsoft.com/en-us/azure/api-management/validate-jwt-policy) policy. |
38 | | [oauth-proxy-session-fragment.xml](oauth-proxy-session-fragment.xml) | oauth-proxy-session-fragment | A fragment that checks for a Session cookie, and either initiate a sign-in flow, or attaches valid tokens to the ongoing request. This will refresh tokens if necessary | Place inside the `````` policy of any Web Apps you want to protect with a session cookie |
39 | | [oauth-proxy-construct-authorization-redirect-fragment.xml](oauth-proxy-construct-authorization-redirect-fragment.xml) | oauth-proxy-construct-authorization-redirect-fragment | A fragment that constructs an OIDC Authorize request to your endpoint | If using a different IdP, use ```oauth-proxy-construct-authorization-redirect.xml``` as a guide to configuring for your IdP |
40 | | [oauth-proxy-slide-session-fragment.xml](oauth-proxy-slide-session-fragment.xml) | oauth-proxy-slide-session-fragment | A fragment that slides any issued session cookie | Place inside the `````` policy of any Web Apps you want to protect with a session cookie |
41 |
42 | ## Policies for Oidc Endpoints
43 | | Policy Name | API path | Method | Purpose | How to use |
44 | | -- | -- | -- | -- | -- |
45 | | [oauth-proxy-sign-in.xml](oauth-proxy-sign-in.xml) | ```/oauth/signin``` | GET | Initiates a front-channel code / pkce flow with an IdP | Configure this as the 'signin' operation within an API called 'OAuth' |
46 | | [oauth-proxy-callback.xml](oauth-proxy-callback.xml) | ```/oauth/callback``` | POST | Handles an IdP's callback in response to a sign-in request | Configure this as the 'callback' operation within an API called 'OAuth' |
47 | | [oauth-proxy-sign-out.xml](oauth-proxy-sign-out.xml) | ```/oauth/signout``` | GET | Clears a user's session cookie, and removes all token data from the cache. | Configure this as the 'signout' operation within an API called 'OAuth' |
48 |
49 | ## Simple policy to protect Web Applications
50 |
51 | ```xml
52 |
53 |
54 |
55 |
56 |
57 |
58 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | ```
81 |
82 |
83 | # Policy Details
84 |
85 | ## oauth-proxy-session-fragment
86 |
87 | ### Purpose
88 | This fragment is intended to sit at a high-level around any calls you need to protect.
89 |
90 | It checks for an incoming session-cookie and either
91 | - Redirects to oauth/signin if invalid / expired
92 | - Fetches tokens from Redis, and decrypts them using the Session-Id (IV), and the TokenEncryptionKey(1 or 2) key
93 | - Renews access tokens using a refresh-token if they are nearing expiration
94 | - Appends the Bearer token to the request for use by the downstream API.
95 |
96 | ## oauth-proxy-construct-authorization-redirect-fragment
97 |
98 | ### Purpose
99 | Assigns a valid URI to the ```oauth-proxy-redirect``` variable that will redirect a User to an IdP to initiate a sign-in.
100 |
101 | The following variables are set by the ```oauth-proxy-sign-in``` policy to use here:
102 | - state
103 | - nonce
104 | - codeChallengeSha256
105 |
106 | This fragment must set a variable called ```oauth-proxy-redirect``` which initiates the sign-in flow.
107 |
108 | ## oauth-proxy-slide-session-fragment
109 |
110 | ### Purpose
111 | An Outbound processing fragment that slides the current session cookie. Use it at the same API scope as the above Session check fragment.
112 |
113 | ### Steps
114 | - Issues a new session cookie on all requests, which slides forward to ```UtcNow + SessionCookieExpirationInSeconds```
115 |
116 | ## oauth-proxy-token-endpoint-fragment
117 |
118 | ### Purpose
119 | This fragment creates a valid URI that where we can exchange a code for a token.
120 |
121 | This fragment must set a variable called ```idpTokenEndpoint``` where we can POST to.
122 |
123 |
124 | ## oauth-proxy-sign-in
125 |
126 | ### Purpose
127 | This policy initiates an OIDC 3-legged sign-in flow.
128 |
129 | ### Steps
130 | - Checks for a valid redirect on the incoming URL
131 | - Creates state, nonce, and code-challenges which are stored in Redis
132 | - Uses the ```oauth-proxy-construct-authorization-redirect-fragment``` to construct the authorisation request
133 | - Returns a cookie with a lookup the the above state, nonce and code-challenge
134 | - 302 Redirects the browser to initiate the front-channel sign-in.
135 |
136 | ### Required QueryString Values
137 |
138 | | Query String key | Purpose |
139 | | -- | -- |
140 | | redirect | Url to redirect to after a successful sign-in flow. This must be a root path beginning with '/'. |
141 |
142 | ## oauth-proxy-callback
143 | > Implemented by [oauth-proxy-callback.xml](./oauth-proxy-callback.xml)
144 |
145 | ### Purpose
146 | This policy handles a callback from an IdP to complete an OIDC flow.
147 |
148 | ### Steps
149 | - Get the ```code``` and ```state``` parameter from the incoming querystring
150 | - Check for an incoming ```oidc``` cookie suffixed with the ```state``` parameter
151 | - Lookup the state and nonce properties from cache which were previously stored in the ```signin``` policy
152 | - Return 401 if we cannot find them
153 | - If the state parameter in the querystring from the IdP matches the cookie, and was stored in our cache, then switch the code for a token using a PKCE code-
154 | - Check the nonce in the returned token matches the nonce stored in session
155 | - Return 401 if we cannot match the nonce
156 | - Creates an IV which is round-tripped in the session cookie (not stored server-side)
157 | - Encrypts the tokens using the above IV, and the TokenEncryptionKey(1 or 2)
158 | - Store the encrypted tokens in Redis
159 | - Set a session-cookie which comprises of our cache-key, the IV, the cookies expiry timestamp. Signs it using a HMAC-SHA-512 signature creating using the SessionCookieKey(1 or 2) named value.
160 |
161 | ## oauth-proxy-sign-out
162 | > Implemented by [oauth-proxy-signout.xml](./oauth-proxy-signout.xml)
163 |
164 | ### Purpose
165 | This policy performs a User 'sign-out'. It removes the session cookies, and also removes all tokens from cache.
166 |
167 | ### Steps
168 | - Clears all cached tokens
169 | - Clears the session cookie
170 | - Redirects the user to the provided redirect parameter (returns a 200 OK if no, or invalid redirect).
171 |
172 | NB: This does not invalidate the access tokens. If any API cached the token it will still be valid until its expiry date.
173 |
--------------------------------------------------------------------------------
/examples/Request OAuth2 access token from SuccessFactors using AAD JWT token.xml:
--------------------------------------------------------------------------------
1 |
5 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | api://{{APIMAADRegisteredAppClientId}}
27 |
28 |
29 | https://login.microsoftonline.com/{{AADTenantId}}/v2.0
30 |
31 |
32 |
33 | user_impersonation
34 |
35 |
36 |
37 |
38 |
39 | gzip, deflate
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | https://login.microsoftonline.com/{{AADTenantId}}/oauth2/v2.0/token
56 | POST
57 |
58 | application/x-www-form-urlencoded
59 |
60 | @{
61 | var _AADRegisteredAppClientId = context.Variables["APIMAADRegisteredAppClientId"];
62 | /*var _AADRegisteredAppClientSecret = context.Variables["APIMAADRegisteredAppClientSecret"];*/
63 | var _EntraIDSAPSFResource = context.Variables["EntraIDSAPSFResource"];
64 | var user_assertion = context.Request.Headers.GetValueOrDefault("Authorization","").Replace("Bearer ","");
65 | var apim_assertion = context.Variables["msi-access-token"];
66 | return $"grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&client_assertion={apim_assertion}&client_id={_AADRegisteredAppClientId}&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&assertion={user_assertion}&scope={_EntraIDSAPSFResource}/.default&requested_token_use=on_behalf_of&requested_token_type=urn:ietf:params:oauth:token-type:saml2";
67 | /* Kept as fallback only: return $"grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion={user_assertion}&client_id={_AADRegisteredAppClientId}&client_secret={_AADRegisteredAppClientSecret}&scope={_EntraIDSAPSFResource}/.default&requested_token_use=on_behalf_of&requested_token_type=urn:ietf:params:oauth:token-type:saml2"; */
68 | }
69 |
70 | ()["access_token"])" />
71 |
72 |
73 | https://{{SAPSFOAuthServerAdressForTokenEndpoint}}/oauth/token
74 | POST
75 |
76 | application/x-www-form-urlencoded
77 |
78 | @{
79 | var _SAPSFApiKey = context.Variables["SAPSFApiKey"];
80 | var _SAPSFCompanyId = context.Variables["SAPSFCompanyId"];
81 | var assertion2 = context.Variables["accessToken"];
82 | return $"grant_type=urn:ietf:params:oauth:grant-type:saml2-bearer&assertion={assertion2}&client_id={_SAPSFApiKey}&company_id={_SAPSFCompanyId}";
83 | }
84 |
85 |
86 | ())" />
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | @(context.Request.Url.ToString())
105 | HEAD
106 |
107 | Fetch
108 |
109 |
110 | @("Bearer " + (string)context.Variables["SAPBearerToken"])
111 |
112 |
113 |
114 |
115 |
116 |
117 | @(((IResponse)context.Variables["SAPCSRFToken"]).Headers.GetValueOrDefault("x-csrf-token"))
118 |
119 |
120 |
121 |
122 |
123 |
124 | @("Bearer " + (string)context.Variables["SAPBearerToken"])
125 |
126 |
127 |
128 |
129 |
130 | json
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
149 |
150 |
151 |
152 |
153 |
154 | @(context.LastError.Source)
155 |
156 |
157 | @(context.LastError.Reason)
158 |
159 |
160 | @(context.LastError.Message)
161 |
162 |
163 | @(context.LastError.Scope)
164 |
165 |
166 | @(context.LastError.Section)
167 |
168 |
169 | @(context.LastError.Path)
170 |
171 |
172 | @(context.LastError.PolicyId)
173 |
174 |
175 | @(context.Response.StatusCode.ToString())
176 |
177 |
178 |
--------------------------------------------------------------------------------
/examples/oauth-proxy/oauth-proxy-session-fragment.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 | x.Trim()).Select(cookie => cookie.Split('=')).SingleOrDefault(cookie => cookie[0] == "{{CookiePrefix}}")?[1] ?? string.Empty : string.Empty)" />
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | @($"/oauth/signin?redirect={(string)context.Variables["redirect"]}")
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | @($"/oauth/signin?redirect={(string)context.Variables["redirect"]}")
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | @($"/oauth/signin?redirect={(string)context.Variables["redirect"]}")
44 |
45 |
46 |
47 |
48 |
49 |
50 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | @($"/oauth/signin?redirect={(string)context.Variables["redirect"]}")
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | @($"/oauth/signin?redirect={(string)context.Variables["redirect"]}")
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | @($"/oauth/signin?redirect={(string)context.Variables["redirect"]}")
105 |
106 |
107 |
108 |
109 |
110 |
111 |
122 |
123 |
124 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 | @($"/oauth/signin?redirect={(string)context.Variables["redirect"]}")
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 | @((string)context.Variables["idpTokenEndpoint"])
171 | POST
172 |
173 | application/x-www-form-urlencoded
174 |
175 | @((string)context.Variables["tokenRefreshData"])
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 | @($"{{CookiePrefix}}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT")
184 |
185 |
186 | @($"/oauth/signin?redirect={(string)context.Variables["redirect"]}")
187 |
188 |
189 |
190 |
191 |
192 |
193 | ())" />
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
211 |
212 |
222 |
223 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 | @($"Bearer {(string)context.Variables["accessToken"]}")
249 |
250 |
251 | @((string)context.Variables["idToken"])
252 |
253 |
254 |
255 |
256 |
257 | @(((Jwt)context.Variables["idTokenJwt"]).Claims["name"][0])
258 |
259 |
260 |
261 |
262 |
263 |
264 | @(((Jwt)context.Variables["idTokenJwt"]).Claims["preferred_username"][0])
265 |
266 |
267 |
268 |
269 |
--------------------------------------------------------------------------------
/examples/oauth-proxy/oauth-proxy-callback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | x.Trim()).Select(cookie => cookie.Split('=')).Single(cookie => cookie[0] == $"{{CookiePrefix}}-{(string)context.Variables["state"]}")[1])" />
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | @($"{{CookiePrefix}}-{(string)context.Variables["state"]}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT")
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
41 |
42 |
43 |
44 |
45 |
46 | @((string)context.Variables["idpTokenEndpoint"])
47 | POST
48 |
49 | application/x-www-form-urlencoded
50 |
51 | @((string)context.Variables["tokenData"])
52 |
53 |
54 |
55 |
56 |
57 |
58 | @($"{{CookiePrefix}}-{(string)context.Variables["state"]}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT")
59 |
60 |
61 |
62 |
63 | ())" />
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
107 |
117 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
150 |
151 |
152 |
153 | @($"{{CookiePrefix}}-{(string)context.Variables["state"]}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT")
154 |
155 |
156 | @($"{{CookiePrefix}}={(string)context.Variables["encryptedCookie"]}; SameSite=Lax; secure; path=/; expires={DateTimeOffset.FromUnixTimeMilliseconds((long)context.Variables["cookie-expiry"]).ToString("R")}; Secure; HttpOnly")
157 |
158 |
159 | @((string)context.Variables["expected-state"])
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 | @($"{{CookiePrefix}}-{(string)context.Variables["state"]}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT")
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 | @($"{{CookiePrefix}}-{(string)context.Variables["state"]}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT")
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 | @($"{{CookiePrefix}}-{(string)context.Variables["state"]}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT")
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
--------------------------------------------------------------------------------