Redirect policy in Azure API Management - azure

Does Azure API Management (Azure APIM) provide any way to redirect urls, in order to replicate Apigee RedirectToLoginPage functionality

You can do it with a policy set at the global (All APIs) scope.
<choose>
<when condition="#(!context.Request.OriginalUrl.Host.Contains("redirect.com"))">
<return-response>
<set-status code="303" reason="See Other" />
<set-header name="Location" exists-action="override">
<value>#("https://redirect.com/" + context.Request.OriginalUrl.Path + context.Request.OriginalUrl.QueryString)</value>
</set-header>
</return-response>
</when>
</choose>

At the time of writing this isn't supported at Azure APIM level. Since I was using IdentityServer for Oauth2 security, I accomplished this by writing an owin/katana authentication middleware. Anders Abel has a great post about it.

Related

Azure API Management with multiple backends

I have an Azure API Management set and running. The API calls are made to a Docker container running FastAPI on an Azure virtual machine. This backend container is responsible for running some AI models based on the queries it receives. It all works fine for a single user.
The thing is: these AI models are defined in a config file inside the container and are user-specific.
I want to use Azure API Management to route requests based on, say, the user subscription. In other words, given a subscription, I want to know which backend to call (each backend would be a container running a specific AI model for that particular user/company on an Azure virtual machine).
What is the best way to approach this?
I found out that you can use inbound policies to specify which backend to use depending on what group the user belongs to. The reference to my answer comes from here.
Go to your API Management, choose Groups and create groups that are relevant to your users. Then add your users to their respective groups.
Go to your API, select inbound rules and specify your backends using something like this:
<policies>
<inbound>
<choose>
<when condition="#(context.User.Groups.Select(g => g.Name).Contains("org1"))">
<set-backend-service base-url="https://abc-apim.azure-api.net/org1app" />
</when>
<when condition="#(context.User.Groups.Select(g => g.Name).Contains("org2"))">
<set-backend-service base-url="https://abc-apim.azure-api.net/org2app" />
</when>
<otherwise>
<return-response>
<set-status code="401" reason="Unauthorized" />
<set-header name="WWW-Authenticate" exists-action="override">
<value>Bearer error="Invalid user group"</value>
</set-header>
</return-response>
</otherwise>
</choose>
<base />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
I undestand this approach is not so elegant, since you have to hard code the backend routing in your policy. But it works.

Azure API management - custom domain specific APIs?

I could successfully add the custom domain to the Azure API management and access the APIs.
I do understand that Azure API management supports multiple custom domains.
However all the APIs are accessible across the custom domains.
Is there any way to limit the APIs per custom domain? Eg: API1 will be available only for the domainX while API2 is accessible for domainY.
You could use an API level or Product level policy to return an Unauthorized status code when the Uri of the incoming call is on the wrong domain:
<policies>
<inbound>
<base />
<choose>
<when condition="#(context.Request.OriginalUrl.Host != "domainX")">
<return-response>
<set-status code="401" reason="Unauthorized"/>
</return-response>
</when>
</choose>
</inbound>
</policies>

Validating Authorization token in incoming requests with Azure API management and third party Authorization Server

I have to implement OAuth 2.0 in my project with a third party Authorization Server. Both my client and server are already registered on the AS. I have created an API management instance on Azure and imported the swagger APIs. I want every incoming request to be validated against my AS, so I just need to redirect the request to https://my-as.com/as/introspect.oauth2, and have the token validated. If the token is valid then let it proceed or else send 401. I was trying to implement this using "inbound processing" and referred to the following doc: https://learn.microsoft.com/en-us/azure/api-management/api-management-howto-protect-backend-with-aad#configure-a-jwt-validation-policy-to-pre-authorize-requests.
The only problem is that instead of Azure AD I'm using a third party AS. I tried replacing the URL in sample XML code with my URL, but its not working.
How do I redirect requests to the Authorization Server for validating access token?
Adding following inbound policy worked:
<inbound>
<!-- Extract Token from Authorization header parameter -->
<set-variable name="token" value="#(context.Request.Headers.GetValueOrDefault("Authorization","scheme param").Split(' ').Last())" />
<!-- Send request to Token Server to validate token (see RFC 7662) -->
<send-request mode="new" response-variable-name="tokenstate" timeout="20" ignore-error="true">
<set-url>https://my-as.com/as/introspect.oauth2</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>#($"grant_type=urn:pingidentity.com:oauth2:grant_type:validate_bearer&client_id=UoM&client_secret=somesecret&token={(string)context.Variables["token"]}")</set-body>
</send-request>
<choose>
<!-- Check active property in response -->
<when condition="#((bool)((IResponse)context.Variables["tokenstate"]).Body.As<JObject>()["active"] == false)">
<!-- Return 401 Unauthorized with http-problem payload -->
<return-response response-variable-name="existing response variable">
<set-status code="401" reason="Unauthorized" />
<set-header name="WWW-Authenticate" exists-action="override">
<value>Bearer error="invalid_token"</value>
</set-header>
</return-response>
</when>
</choose>
<base />
</inbound>

Forward requests to regional API based on a specific JWT claim

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>

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