Set Response Header With remaining-calls Azure API Management - azure

I'm using Azure API Management with some rate limiting based on subscription. I need to send to the user in the response headers the number of remaining calls. I know that I should set some values in the outbound policy but I do not know how to do it exactly. This is my policy XML if any one can help.
<policies>
<inbound>
<base />
<set-variable name="remainingCalls" value="remaining-calls-variable-name" />
<quota-by-key calls="5" renewal-period="86400" counter-key="#(context.Subscription?.Key ?? "anonymous")" increment-condition="#(context.Response.StatusCode >= 200 && context.Response.StatusCode < 300)" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
<set-header name="remainingCalls" exists-action="append">
<value>#(context.Response.Headers.GetValueOrDefault("remaining-calls-header-name","2"))</value>
</set-header>
</outbound>
<on-error>
<base />
</on-error>
</policies>

As per the Azure Documentation, You can set rate-limit by subscription only in inbound section & the policy scope should be either product, api or operation.
Here is the sample example, where the per subscription rate limit is 30 calls per 90 seconds. After each policy execution, the remaining calls allowed in the time period are stored in the variable remainingCallsPerSubscription.
<policies>
<inbound>
<base />
<rate-limit calls="30" renewal-period="90" remaining-calls-variable-name="remainingCallsPerSubscription"/>
</inbound>
<outbound>
<base />
</outbound>
</policies>
Note: This policy can be used only once per policy document.
Policy expressions cannot be used in any of the policy attributes for this policy.

I've contacted Microsoft Azure support for this request and they were able to guid me to a possible workaround that may be helpful and in my particular use case it is good solution. For quota policy and as mentioned by #Venkatesh-MAT it is not supported to retrieve remaining quota information in response header as rate-limit policy. However there is a separate REST API for this purpose. This is documentation for the same https://learn.microsoft.com/en-us/rest/api/apimanagement/current-ga/quota-by-counter-keys/list-by-service.
The API in this documentation requires bearer token as authentication. To be able to generate the bearer token you can simply use azure cli to get token for the resource using command az account get-access-token --resource https://management.azure.com or if you need to do it programmatically you have to follow below steps:
Set principle role using azure cli with subscription scope to create service principle that have access on this resource scope (az ad sp create-for-rbac -n "principle-1" --role contributor –scopes /subscriptions/{subscriptionID}/resourceGroups/{resourcegroup}/providers/Microsoft.ApiManagement/service/{API management Service name} /quotas/{subscription key})
Use Client ID, client secret & tenant ID generated from above step to call this API https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token with body type x-www-form-urlencoded and body key value as below
KEY: grant_type VALUE: client_credentials
KEY: client_id VALUE: appid generated from step number 1
KEY: scope VALUE: https://management.azure.com/.default
KEY: client_secret VALUE: password generated from step number 1
Then use the output access token to get quota policy consumption.

Related

Unauthorized Issue while Authenticating Azure Function with APIM

I created 1 Azure Function app (.NET Core 6) with a few functions.
Function Details:
ListUsers - GET Method with Routing(v1/listusers)
URL: https://samplefunction1.azurewebsites.net/api/v1/listusers
Step1: I registered a new Azure B2C Application for function app in B2C Tenant with Redirect URL as JWT MS (For testing purpose) and created B2C_1A_SIGNUP_SIGNIN custom policy in to get token.
Step2: I enabled Managed Identity (System Identity) ON for function app.
Step3: I provided authentication to azure function app with identity provider OPENID Connect and configured metadataURL, Client Id ,CLient Secret etc., of Step1 endpoint details
Step4: I Created APIM resource, enabled managed Identity(System Identity) and imported Function App and can see apim-Samplefunction host keys under function app keys.
Step4: I Created APIM resource and imported Function App added APIM Policy in under all operations and enabled CORS.
//Resource Id is taken from Step1 (ClientID)
<authentication-managed-identity resource="63b20196-e62b-4cf0-a60e-9e895ee5f1a2" />
Step5: I tested function URL in postman with authentication and host key and its success.
URL: GET METHOD
https://samplefunction1.azurewebsites.net/api/v1/listusers?code=<<HOST KEY Taken from Azure Function APP>>
Header:
Authorization : Bearer <<Token received from JWT.MS web page >>
APIM URL: When i tried same token with APIM Url, I am getting errorYou do not have permission to view this directory or page. with authentication-managed-identity policy with Azure Function App Client Id
I created another B2C application registration for APIM and used authentication-managed-identity policy with Azure APIM Client Id. At this time I am getting Internal Server Error.
My Queries:
Which client id I have to use to authorize APIM Url?
Does both APIM B2C & Azure Function B2C Registration is required?
Get Method
https://sample-apim-poc.azure-api.net/urlsuffix/v1/listusers
Headers:
Ocp-Apim-Subscription-Key:d40b5dfe106d40c0b234cec702173761
Authorization:Bearer <<Token received from JWT.MS web page >>
Ocp-Apim-Trace:true
Content-Type:application/json
Inbound Policy settings for All Operations:
<!--
IMPORTANT:
- Policy elements can appear only within the <inbound>, <outbound>, <backend> section elements.
- To apply a policy to the incoming request (before it is forwarded to the backend service), place a corresponding policy element within the <inbound> section element.
- To apply a policy to the outgoing response (before it is sent back to the caller), place a corresponding policy element within the <outbound> section element.
- To add a policy, place the cursor at the desired insertion point and select a policy from the sidebar.
- To remove a policy, delete the corresponding policy statement from the policy document.
- Position the <base> element within a section element to inherit all policies from the corresponding section element in the enclosing scope.
- Remove the <base> element to prevent inheriting policies from the corresponding section element in the enclosing scope.
- Policies are applied in the order of their appearance, from the top down.
- Comments within policy elements are not supported and may disappear. Place your comments between policy elements or at a higher level scope.
-->
<policies>
<inbound>
<base />
<cors allow-credentials="false">
<allowed-origins>
<origin>*</origin>
</allowed-origins>
<allowed-methods>
<method>GET</method>
<method>POST</method>
</allowed-methods>
</cors>
<authentication-managed-identity resource="13b20196-e62b-4cf0-a60e-9e895ee5f1a2" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>

API Management - Set Backend Service as Operation Policy in Terraform

I'm creating an APIM in Azure using Terraform. So far I've been able to create the APIM instance, the API and an operation within the API. Because I want each operation for the API to point to an individual Logic App, my understanding is I cannot set this as an azurerm_api_management_backend and instead need to set it in the operation policy in XML.
This is what my operation policy looks like:
resource "azurerm_api_management_api_operation_policy" "apim1_ss_cmpcomplaints_api_dev_get_policy" {
api_name = azurerm_api_management_api.apim1_ss_api_dev.name
api_management_name = azurerm_api_management.test-apimManagement.name
resource_group_name = azurerm_resource_group.apimResourceGroup.name
operation_id = "get-complaints"
xml_content = <<XML
<policies>
<inbound>
<base />
<set-method id="apim-generated-policy">GET</set-method>
<set-backend-service id="apim-generated-policy" backend-id="/subscriptions/xxx/resourceGroups/hm-iac-msdn-neu-rg/providers/Microsoft.Logic/workflows/testLogicApp" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
XML
}
I have created the Logic App and retrieved the resource ID from the Azure CLI and included it in the set-backend-service node. But despite getting the ID from the CLI, I am getting the following response:
│ Error: creating or updating API Operation Policy (Resource Group "apim-resource-group" / API Management Service "harry-test-apim" / API "test_api_dev" / Operation "get-complaints"): apimanagement.APIOperationPolicyClient#CreateOrUpdate: Failure responding to request: StatusCode=400 -- Original Error: autorest/azure: Service returned an error. Status=400 Code="ValidationError" Message="One or more fields contain incorrect values:" Details=[{"code":"ValidationError","message":"Error in element 'set-backend-service' on line 5, column 10: Backend with id '/subscriptions/xxx/resourceGroups/hm-iac-msdn-neu-rg/providers/Microsoft.Logic/workflows/testLogicApp' could not be found.","target":"set-backend-service"}]
I'd prefer to set this using the resource ID instead of using base-url.
The backend-id is different to the ARM id. It's not easy to find, so the solution is to generate the policy in the APIM front-end and then apply that in code afterwards.

Use Azure App Service Authentication to log into Api Management Service

I was able to setup Azure Active Directory (aad) to authenticate users for the App Service running an angular front-end, now I want to secure the backend by allowing only this authenticated users to send requests to the Api Management Service endpoint.
So, I followed this article https://learn.microsoft.com/en-us/azure/api-management/api-management-howto-protect-backend-with-aad and I am facing 2 major problems:
The /.auth/me endpoint only returns an id_token, not an access_token
When I try with postman, I keep on getting invalid audience, but postman uses an audience that looks like 00000-00000...
Here's the api management service inbound request policy jwt-validate:
<policies>
<inbound>
<base />
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="failed message">
<openid-config url="https://login.microsoftonline.com/{tenant-id}/.well-known/openid-configuration" />
<audiences>
<audience>{app-id-uri}></audience>
</audiences>
</validate-jwt>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
For your first problem:
Actually, it can just response id token when you request the /.auth/me endpoint because you just implement login but not specify a resource(or a backend app/api protected by AD) in your app service. So the response contains id token and without access token.
If you want to get access token when you request /.auth/me endpoint, you need to specify the resource in your app service. Here I assume you have already registered an app in Azure AD to represent your APIM. Then you can use the Resource Explore to modify the settings like below:
1 . locate your app service
2 . locate the config->authsettings (the resource below is the client id of the app you registered to represent your APIM)
"additionalLoginParams": [
"response_type=code id_token",
"resource=3fa9607b-63cc-4050-82b7-91e44ff1df38"
],
3 . config the redirect_uri for Azure app like below:
https://yourapp.azurewebsites.net/.auth/login/aad/callback
Then after you login in the angular app, you can get the access_token via the endpoint:
https://yourapp.azurewebsites.net/.auth/me
The steps above come from this post, you can also refer to it for detail information.
For your second problem:
When you request the APIM api in postman, you should put the access token in the request header. And you mentioned that the access token which you used in postman contains the audience like 00000-00000... But in your validate-jwt policy, you use:
<audiences>
<audience>{app-id-uri}</audience>
</audiences>
It seems {app-id-uri} is not only a id but should be a uri like: api://00000-00000.... So please change {app-id-uri} from uri format to id format (just remove api://).

Create request rate limiting for all API instances under APIM

Is it possible to create a policy that limites request rate over a period for all API instances?
Can the policy below be used for the requirement?
Please note I want the policy to be define in one place (global, that is, "All APIs"), not repeated for every API instance.
<policies>
<inbound>
<base />
<rate-limit calls="20" renewal-period="90" />
</inbound>
<outbound>
<base />
</outbound>
</policies>
Policy sections: inbound
Policy scopes: product
https://learn.microsoft.com/en-us/azure/api-management/api-management-access-restriction-policies#LimitCallRate
yes. You can add this policy inside "ALL APIS" policy section. It will get effect for all the APIs. Its's correct

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>

Resources