Forward requests to regional API based on a specific JWT claim - azure

Is it possible to forward requests to regional API based on a specific JWT claim?
The platform I'm working on has one API per region, and our customers are required to know it in order to build the base request URL - e.g.: https://{region}.service.com
Unfortunately, the only reliable way to try and figure out which regional api to call automatically from Azure APIM (e.g.: calling a single endpoint at https://api.service.com), in our scenario, would be by analyzing a claim that always comes with the bearer token (which we already do at the APIM level.)
Has anybody had the need to do it this way? Thanks in advance!

APIM policy expressions along with "choose" policy allow you to create arbitrary processing logic: https://learn.microsoft.com/en-us/azure/api-management/api-management-policy-expressions.
Access to JWT is available as
context.Request.Headers.GetValueOrDefault("Authorization").AsJwt()
It returns Jwt object (look for it's properties on the same page above).
All this combined with "set-backend-service" policy should be sufficient to do the job.

Vitaly's answer was the key to figuring this one out. Here is the complete answer, in case anybody is looking for the same thing.
<policies>
<inbound>
<!-- Extract Token from Authorization header parameter -->
<set-variable name="token" value="#(context.Request.Headers.GetValueOrDefault("Authorization",string.Empty).Split(' ').Last().AsJwt())" />
<choose>
<when condition="#(context.Variables["token"] != null)">
<set-variable name="api_uri" value="#(((Jwt)context.Variables["token"]).Claims.GetValueOrDefault("api_uri", string.Empty))" />
<choose>
<when condition="#(context.Variables["api_uri"] != string.Empty)">
<set-backend-service base-url="#((string)context.Variables["api_uri"])" />
</when>
<otherwise />
</choose>
</when>
<otherwise />
</choose>
<base />
</inbound>
</policies>

Related

Change APIM Subscription key header name that's already in use

We are currently using the default Ocp-Apim-Subscription-Key header name for Azure APIM subscriptions, and it is used in production by a fair amount of people. We would like to change that without breaking any existing integration, by for example adding support for a second ApiKey header that fulfils the same role, until we can phase out the default APIM one.
Azure only offers the option to change the existing one - which would be a breaking change in our case. How can we deprecate this header safely in favor of a more readable one?
That is not going to be trivial, unfortunately. But there is a way. If you change API's subscription key header name any request coming in with old header will produce 401 response. You could intercept that in API's on-error section and check if old header name is present or not, and if it does - reissue the request. Something along these lines:
<on-error>
<base />
<choose>
<when condition="#(context.Response.StatusCode == 401 && context.Request.Headers.ContainsKey("OCP-APIM-Subscription-Key"))">
<send-request mode="copy" response-variable-name="response">
<set-url>#{
var urlParts = context.Request.OriginalUrl.ToString().Split('/');
urlParts[2] = "127.0.0.1"; //Overriding host to keep request local, but Host header also need to be reset to make SSL work
return string.Join("/", urlParts);
}</set-url>
<set-header name="Host">
<value>#(context.Request.OriginalUrl.Host)</value>
</set-header>
<set-header name="ApiKey">
<value>#(context.Request.Headers.GetValueOrDefault("OCP-APIM-Subscription-Key"))</value>
</set-header>
</send-request>
<return-response response-variable-name="response" />
</when>
</choose>
</on-error>

Can I call different backend on Azure API Management

I am trying to achieve that, when I call an APIM Endpoint based on the request headers It should different endpoint. for e.g when user call https://test.azure-api.net/testsvc-dev/api/test APIM should be able to send the request to https://testappv1:80/test or https://testappv2:80. right now I can see in the serviceURL, I can add only one. Is there any policy that I can use to do these kind of operations.
little more context: I am trying to access two different version of API on a single call. Instead of caller choosing which one to call,I am trying to make the APIM to decide based on the user.
You are probably looking for the set-backend-service policy.
Here an example from the Microsoft docs that changes the backend service based on a query parameter:
<policies>
<inbound>
<choose>
<when condition="#(context.Request.Url.Query.GetValueOrDefault("version") == "2013-05")">
<set-backend-service base-url="http://contoso.com/api/8.2/" />
</when>
<when condition="#(context.Request.Url.Query.GetValueOrDefault("version") == "2014-03")">
<set-backend-service base-url="http://contoso.com/api/9.1/" />
</when>
</choose>
<base />
</inbound>
<outbound>
<base />
</outbound>
</policies>
Certainly, you could adopt the sample and query for the desired headers ;-)

How to setup 405 Method Not Allowed for each of the methods in APIs using azure API Management

In azure API Management how to setup the 405 (Method not allowed) policy. I am using azure API management APIs and adding different policies like jwt validation, IP filtering, rate limit and all. But I couldn't find a way for add the 405 method not allowed in APIM. I want to setup this for each of the methods. That means I want to block the incoming unrecognized method requests from APIM. (eg: Get instead of POST (Throws 405 method not allowed from APIM). Currently APIM passes the wrong method to backend and it returns the 404 from the application. Anyone know how we can block the wrong request from APIM side and returns 405 instead of passing it to backend and returns 404?.
You could use a Control Flow policy along with the Context Variable on the Inbound policy of each Method to intercept any requests that don't match the defined http method and then use a Set Status policy to return a 405. So for a GET method something along the lines of:
<policies>
<inbound>
<choose>
<when condition="#(context.Request.Method.ToString() != "GET")">
<return-response>
<set-status code="405" reason="No Content" />
</return-response>
</when>
</choose>
<base />
</inbound>
... rest of policies
</policies>
If you've got multiple methods with the same path you might need to apply this at the API level rather than the Method level and make the condition equals methods not in use rather than not equal to method in use
To set this at the API level and check against a collection of methods not in use create a policy along the lines of:
<policies>
<inbound>
<choose>
<when condition="#{
ICollection<string> disallowedMethods = new List<string>() { "POST", "PUT" };
return disallowedMethods.Contains(context.Request.Method.ToString());
}">
<return-response>
<set-status code="405" reason="No Content" />
</return-response>
</when>
</choose>
<base />
</inbound>
... rest of policies
</policies>
The http methods not in use in this example are POST and PUT but you can change the list to whatever applies in your use case.

Azure API management POST request - All body parameters required

How can I apply validation that all request parameters in body are required for my post request? What policy expressions I may use?
I am using following expression:
<policies>
<inbound>
<base />
<choose>
<when condition="#((context.Request.Body) != null&& ((int)context.Request.Body.As<JObject>()["Id"])>0)">
<return-response>
</return-response>
</when>
<otherwise>
<return-response>
</return-response>
</otherwise>
</choose>
</inbound>
How can I restrict to input all body parameters for this post request?
See APIM's content validation policies: https://learn.microsoft.com/en-us/azure/api-management/validation-policies#validate-content They allow you to validate that request actually conforms to the schema specified in your spec.
As an alternative you could read body as JObject and manually checking each property of interest. Mind that you want to use context.Request.Body.As(preserveContent: true) to ensure that body is cached and available to later be sent to backend.

Is it possible to use the subscription-key query string parameter with Azure API Management SOAP-passthrough?

We use API Management to expose several API's. One of the API's we expose is configured as a SOAP-passthrough API but we are facing some issues with it regarding authentication of APIM.
When we use the Ocp-Apim-Subscription-Key header for passing the query string it all works correct and the API is returning it's content correct.
When we use the subscription-key query string parameter the API is returning a 401 Unauthorized. I tested this behavior in Postman and changing the way of sending the subscription key is resulting in this behavior.
An implementation detail of this API is that it exposes an existing WSDL and routes this SOAPAction to an Azure Function via the policy. In the Application Insights of the function I can verify that the function is never invoked when I get a 401 but it is invoked when I get a successful call (using the header).
Is this normal behavior? Am I doing things wrong? Or is it a bug in APIM?
This might be an issue with the way we do routing for SOAP Passthrough. You will notice in the API setup that we add on a query parameter to identify the SoapAction that an operation will be matched to. It may be that your the api key query parameter is getting overwritten when adding the SoapAction parameter to the inbound request. I will investigate and let your know.
We currently use a workaround around this problem with the following policy. Instead of changing the backend-server url in the policy we send a request and set the response of that request as a response for this api. Below you can find our policy which is working with the subscription-key in the query string.
<policies>
<inbound>
<base />
<send-request mode="copy" response-variable-name="response" timeout="20" ignore-error="false">
<set-url>{{BackendServer_URL}}</set-url>
</send-request>
<!--return-response response-variable-name="reponse" /-->
<choose>
<!-- If StatusCode is not OK, return Unauthorized with the reason. -->
<when condition="#(((IResponse)context.Variables["response"]).StatusCode != 200)">
<return-response response-variable-name="reponse">
<set-status code="401" reason="Unauthorized" />
<set-body>#(((IResponse)context.Variables["response"]).Body.As<string>())</set-body>
</return-response>
</when>
<otherwise>
<return-response response-variable-name="reponse">
<set-status code="200" />
<set-header name="Content-Type" exists-action="override">
<value>text/xml; charset=utf-8</value>
</set-header>
<set-body>#(((IResponse)context.Variables["response"]).Body.As<string>())</set-body>
</return-response>
</otherwise>
</choose>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>

Resources