Goal: Use API Management cache-policy to cache a json response that never changes.
Original (no cache)
This policy is backed by an azure function that returns a never-changing json response.
<policies>
<inbound>
<base />
<set-backend-service id="apim-generated-policy" backend-id="azfunc-fluffyoauth2" />
<rewrite-uri template="/WellKnownOpenidConfiguration" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Thinking that this might be a good thing to cache, I introduce the cache-lookup policy.
<policies>
<inbound>
<base />
<set-backend-service id="apim-generated-policy" backend-id="azfunc-fluffyoauth2" />
<rewrite-uri template="/WellKnownOpenidConfiguration" />
<cache-lookup vary-by-developer="false" vary-by-developer-groups="false" downstream-caching-type="none" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
<cache-store duration="36000" />
</outbound>
<on-error>
<base />
</on-error>
</policies>
What I am seeing is that the cached version returns a binary that I believe is gzipped. This causes downstream code that expects it to be raw json to break. This reponse is an openid connect discovery document, and ironically if I point API Management's jwt-policy to this it breaks as well.
i.e.
var auth0Domain = "https://apim-mycompany.azure-api.net/oauth2";
string responseString = null;
try
{
var url = $"{auth0Domain}/.well-known/openid-configuration-cached";
Console.WriteLine($"-------------------");
Console.WriteLine($"{url}");
var httpClient = new HttpClient();
var response = await httpClient.GetAsync(new Uri($"{auth0Domain}/.well-known/openid-configuration-cached"));
responseString = await response.Content.ReadAsStringAsync();
Console.WriteLine($"-------------------");
Console.WriteLine($"{responseString}");
Console.WriteLine($"-------------------");
JsonConvert.DeserializeObject<OpenIdConnectConfiguration>(responseString);
}
catch (Exception ex)
{
Console.WriteLine($"Error:{ex.Message}");
Console.WriteLine($"-------------------");
return;
}
curl -I --location --request GET 'https://apim-fluffyoauth2.azure-api.net/oauth2/.well-known/openid-configuration-cached'
HTTP/1.1 200 OK
Cache-Control: no-store, must-revalidate, no-cache
Pragma: no-cache
Content-Length: 558
Content-Type: application/json; charset=utf-8
Content-Encoding: gzip
Expires: Tue, 14 Apr 2020 18:50:53 GMT
Vary: Accept-Encoding
Request-Context: appId=cid-v1:dfeba42c-e636-42bc-b501-7c77563c3e7b,appId=cid-v1:dfeba42c-e636-42bc-b501-7c77563c3e7b
Date: Tue, 14 Apr 2020 18:50:53 GMT
Question(s):
What is the cache-policy doing?
How can I change it to still cache but respond with what the NON-CACHED version does?
Adding the following;
<cache-lookup vary-by-developer="false" vary-by-developer-groups="false" downstream-caching-type="none">
<vary-by-header>Accept</vary-by-header>
<vary-by-header>Accept-Charset</vary-by-header>
</cache-lookup>
Which produces the final policy.
<policies>
<inbound>
<base />
<set-backend-service id="apim-generated-policy" backend-id="azfunc-fluffyoauth2" />
<rewrite-uri template="/WellKnownOpenidConfiguration" />
<cache-lookup vary-by-developer="false" vary-by-developer-groups="false" downstream-caching-type="none">
<vary-by-header>Accept</vary-by-header>
<vary-by-header>Accept-Charset</vary-by-header>
</cache-lookup>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
<cache-store duration="3600" />
</outbound>
<on-error>
<base />
</on-error>
Now results in API Management not doing a gzip encoding when it wasn't asked for.
I am still wondering why API Management will suddenly stop doing gzip encoding on the original problem. Sometimes it did it, then stop, which lead to some confusion as to what was going on.
Related
I have the following policy fragment that connects to an API we are using for authorization.
<fragment>
<send-request mode="new" response-variable-name="bearerToken" timeout="20" ignore-error="true">
<set-url>{{url}}</set-url>
<set-method>POST</set-method>
<set-header name="Content-Type" exists-action="override">
<value>application/x-www-form-urlencoded</value>
</set-header>
<set-body>#{ return "{{target-url}}"; }</set-body>
</send-request>
<set-header name="Authorization" exists-action="override">
<value>#("Bearer " + (String)((IResponse)context.Variables["bearerToken"]).Body.As<JObject>()["access_token"])</value>
</set-header>
</fragment>
This works fine with no issues, however, I would like to capture the information pushed to the Authorization Header and push it to a variable for later use in my APIs.
When I do something similar to the following or it's variants, I only receive errors and no information as to what has happened.
<set-variable name="BearerToken" value="#("Bearer " + (String)((IResponse)context.Variables["bearerToken"]).Body.As<JObject>()["access_token"]" />
I'm not sure if this is an order of execution or something else I might have missed?
Thank you - Greg.
I have an Azure APIM which checks the request bearer JWT token:
<inbound>
<base />
<set-variable name="TenantId" value="
#{
var tenantId = context.Request.Headers.GetValueOrDefault("TenantId","");
if (tenantId != null) {
return tenantId;
}
return null;
}" />
<limit-concurrency key="#((string)context.Variables["TenantId"])" max-count="1">
<set-header name="Ocp-Apim-Subscription-Key" exists-action="delete" />
<set-header name="subscription-key" exists-action="delete" />
<set-variable name="JWTIssuer" value="
#{
var authStr = context.Request.Headers.GetValueOrDefault("Authorization","");
if (authStr.StartsWith("Bearer")) {
var jwt = authStr.Substring("Bearer ".Length).AsJwt();
if (jwt != null) {
return jwt.Issuer;
}
}
return null;
}" />
<choose>
<when condition="#(context.Variables.GetValueOrDefault("JWTIssuer", "") == "<URL of issuer>")">
<!-- Authorisation using HPCA token -->
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized" require-expiration-time="true" require-scheme="Bearer" require-signed-tokens="true">
<openid-config url="<Open Id Url>" />
<required-claims>
<claim name="MyClaim" match="any" separator=",">
<value>X</value>
</claim>
</required-claims>
</validate-jwt>
</when>
<!-- HPCA Error handling -->
<otherwise>
<return-response>
<set-status code="401" reason="Unauthorized" />
<set-header name="WWW-Authenticate" exists-action="override">
<value>Bearer error "Invalid HPCA token"</value>
</set-header>
</return-response>
</otherwise>
</choose>
</limit-concurrency>
</inbound>
<on-error>
<base />
<return-response>
<set-status code="#(context.Response.StatusCode)" reason="#(context.Response.StatusReason)" />
</return-response>
</on-error>
This all works as expected: if either the issuer is incorrect or the JWT token is invalid, then the OnError handler is invoked and a 401 error status is returned to the caller. The problem is, if i add a call to a function after the Choose policy:
</otherwise>
</choose>
<send-request mode="copy" response-variable-name="Response" timeout="10" ignore-error="false">
<set-url>https://myfuncApp.azurewebsites.net/api/Function1</set-url>
<set-method>POST</set-method>
</send-request>
<return-response response-variable-name="Response">
<set-status code="#(context.Response.StatusCode)" reason="#(context.Response.StatusReason)" />
</return-response>
</limit-concurrency>
</inbound>
Then even with a invalid bearer token and the error handler being invoked, the APIM still proceeds to call the function: the return response within the error handler is seemingly ignored - why ? I can fudge it by wrapping the send-request within another conditional (i.e. check the context.Response.StatusCode isn't 401) but this doesn't seem right to me.
For your question, I think the result of your test is normal.
You added the <send-request> to request the function in <inbound> area. The <validate-jwt> can just determine if the APIM to continue request the backend url but can not determine if it continues to request function url in <inbound> area.
I want to change the output format of the soap response, before passing it onto the client. I am using the soap pass-through as specified in the documentation https://azure.microsoft.com/en-ca/blog/soap-pass-through/
Soap service used in this example is hosted at
https://fazioapisoap.azurewebsites.net/FazioService.svc?singleWsdl
I am unable to extract the soap response in the liquid template. Soap response does get generated, however, there is no data.
Is there anything wrong with this code?
<policies>
<inbound>
<set-header name="Content-Type" exists-action="override">
<value>text/xml</value>
</set-header>
<set-body template="liquid">
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
<soapenv:Header />
<soapenv:Body>
<tem:GetOpenOrders>
<!--Optional:-->
<tem:cust>{{body.Envelope.Body.GetOpenOrders.cust}}</tem:cust>
</tem:GetOpenOrders>
</soapenv:Body>
</soapenv:Envelope>
</set-body>
<base />
</inbound>
<backend>
<base />
</backend>
<outbound>
<set-body template="liquid">
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<GetOpenOrdersResponse xmlns="http://tempuri.org/">
<GetOpenOrdersResult xmlns:a="http://schemas.datacontract.org/2004/07/FazioAPISoap" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
{% for summary in body.Envelope.Body.GetOpenOrdersResponse.GetOpenOrdersResult.OrderSummary -%}
<a:OrderSummary><a:order_id>{{summary.order_id}}</a:order_id></a:OrderSummary>
{% endfor -%}
</GetOpenOrdersResult>
</GetOpenOrdersResponse>
</s:Body>
</s:Envelope>
</set-body>
<set-header name="Content-Type" exists-action="override">
<value>text/xml</value>
</set-header>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
You don't have to prefix the namespaces. This requests is valid
<set-body template="liquid">
<Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<Body>
<GetOpenOrdersResponse xmlns="http://tempuri.org/">
<GetOpenOrdersResult xmlns:a="http://schemas.datacontract.org/2004/07/FazioAPISoap" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
{% for summary in body.Envelope.Body.GetOpenOrdersResponse.GetOpenOrdersResult.OrderSummary -%}
<OrderSummary>
<order_id>
{{summary.order_id}}
</order_id>
</OrderSummary>
{% endfor -%}
</GetOpenOrdersResult>
</GetOpenOrdersResponse>
</s:Body>
</s:Envelope>
</set-body>
I can see two potential problems with your request.
You might be missing SOAPAction header, example for submitOrder
<set-header name="SOAPAction" exists-action="override">
<value>http://tempuri.org/IFazioService/submitOrder</value>
</set-header>
Also you may want to set backend url and rewrite path to the resource (if APIM path is different than the SOAP service)
<set-backend-service base-url="{{s-iserve-store-inventory-backend-url}}" />
<rewrite-uri template="?" copy-unmatched-params="false" />
In my Azure API Management Policy I am checking for some headers and do certain actions depending on what is found.
How do I throw an error when none of the conditions are matched (i.e. in the otherwise block)
<policies>
<inbound>
<choose>
<when condition="">
</when>
<when condition="">
</when>
<otherwise>
</otherwise>
</choose>
<base/>
</inbound>
<backend>
<base/>
</backend>
<outbound>
<base/>
</outbound>
<on-error>
<base/>
</on-error>
</policies>
I probably want to return a 401 since I am checking groups in the headers.
You can use a <choose> policy to detect and report failure, return a 401 response.
<otherwise>
<return-response >
<set-status code="401" reason="Unauthorized" />
<set-header name="WWW-Authenticate" exists-action="override">
<value>Bearer error="invalid_token"</value>
</set-header>
</return-response>
</otherwise>
Here is also a similar SO thread you could refer to.
In API Management, how do you access URL template parameters through a policy?
In this instance, my operation is called test, the HTML verb is GET, and the URL template is as below -
/test/{variable_name1}/{variable_name2}
I was under the impression that accessing the value of a parameter was as simple as {variable_name1}. However, the example below does not set the variable "rowkey" as expected. Rather it has a value of {variable_name1}-{variable_name2}.
What am I doing wrong here?
<policies>
<inbound>
<set-variable name="rowkey" value="{variable_name1}-{variable_name2}" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<set-header name="Row-Key" exists-action="override">
<value>#((string)context.Variables["rowkey"])</value>
</set-header>
</outbound>
</policies>
You'd have to use expressions to achieve what you want, like:
<set-variable
name="rowkey"
value="#(context.Request.MatchedParameters["variable_name1"] + "-" + context.Request.MatchedParameters["variable_name2"])" />
or use string interpolation:
<set-variable
name="rowkey"
value="#($"{context.Request.MatchedParameters["variable_name1"]}-{context.Request.MatchedParameters["variable_name2"]}")" />