In this post let's see how we can customize Azure API Management (APIM)
Policy to enrich requests with additional headers and also let's look at how we can make
use of caching.
The demo scenario is as follows:
- I have an example BE API, which requires a specific HTTP Header: X-Tenant-Code, in order for it to serve.
- A client sends a request with JWT Token, but the client has no knowledge of X-Tenant-Code HTTP header.
- Inside the APIM, we need to inspect the JWT Token, and based on a Claim in the token (in this case, it's AppId/ClientId), we need to find out Tenant information by calling another API endpoint, let's say Tenant API. And then populate X-Tenant-Code HTTP header from the response data.
- We don't need to be calling the Tenants API all the time, instead, we can cache Tenant information in APIM itself (while we can use the inbuilt cache, it's recommended to use external Cache, currently only Redis is supported)
Now let's start.
1: The first step is accessing the JWT token and extracting
claims. (Read more on how to access JWT token:
API Management policy expressions)
<!-- Get ClientId From JWT Token -->
<set-variable name="clientid"
value="@(context.Request.Headers.GetValueOrDefault("Authorization","").Split(' ')[1].AsJwt()?.Claims.GetValueOrDefault("appid"))" />
2: Now I am checking whether the Tenant Information already exists in
APIM Cache for the given ClientId.
<!--Look for tenantinfo for this specific client in the cache and set it to context variable 'tenantinfo' -->
<cache-lookup-value key="@("tenantinfo-" + context.Variables["clientid"])" variable-name="tenantinfo" />
3: Now I need to have a conditional expression. That is, if the item is
not in the cache, then I need to call another API endpoint, passing in the
ClientId to get the Tenant information. Let's assume, the endpoints return a JSON
response.
<choose>
<!-- Didn't find tenantinfo in cache -->
<when condition="@(!context.Variables.ContainsKey("tenantinfo"))">
<!-- Make a HTTP request to GET Tenant, copying current HTTP request headers -->
<!-- Store the response in context variable 'tenantinforesponse' -->
<send-request mode="copy" response-variable-name="tenantinforesponse" timeout="10" ignore-error="true">
<set-url>@("https://app-azarch-tenants-service-001.azurewebsites.net/" + context.Variables["clientid"])</set-url>
<set-method>GET</set-method>
</send-request>
<!-- Store response body in context variable 'tenantinfo' as a JObject -->
<set-variable name="tenantinfo" value="@(((IResponse)context.Variables["tenantinforesponse"]).Body.As<JObject>())" />
<!-- Store result in cache for 1 day (86400 seconds) -->
<cache-store-value key="@("tenantinfo-" + context.Variables["clientid"])" value="@((JObject)context.Variables["tenantinfo"])" duration="86400" />
</when>
</choose>
4: At this state, the context variable: tenantinfo , is populated. And now we can use it to populate X-Tenant-Code header.
<!-- Set the X-Tenant-Code header to the tenantCode value from the context variable 'tenantinfo' -->
<set-header name="X-Tenant-Code" exists-action="override">
<value>@((string)((JObject)context.Variables["tenantinfo"]).GetValue("tenantCode"))</value>
</set-header>
The complete policy looks as follows.
<policies>
<inbound>
<!-- Get ClientId From JWT Token -->
<set-variable name="clientid"
value="@(context.Request.Headers.GetValueOrDefault("Authorization","").Split(' ')[1].AsJwt()?.Claims.GetValueOrDefault("appid"))" />
<!--Look for tenantinfo for this specific client in the cache and set it to context variable 'tenantinfo' -->
<cache-lookup-value key="@("tenantinfo-" + context.Variables["clientid"])" variable-name="tenantinfo" />
<choose>
<!-- Didn't find tenantinfo in cache -->
<when condition="@(!context.Variables.ContainsKey("tenantinfo"))">
<!-- Make a HTTP request to GET Tenant, copying current HTTP request headers -->
<!-- Store the response in context variable 'tenantinforesponse' -->
<send-request mode="copy" response-variable-name="tenantinforesponse" timeout="10" ignore-error="true">
<set-url>@("https://app-azarch-tenants-service-001.azurewebsites.net/" + context.Variables["clientid"])</set-url>
<set-method>GET</set-method>
</send-request>
<!-- Store response body in context variable 'tenantinfo' as a JObject -->
<set-variable
name="tenantinfo"
value="@(((IResponse)context.Variables["tenantinforesponse"]).Body.As<JObject>())" />
<!-- Store result in cache for 1 day (86400 seconds) -->
<cache-store-value
key="@("tenantinfo-" + context.Variables["clientid"])"
value="@((JObject)context.Variables["tenantinfo"])"
duration="86400" />
</when>
</choose>
<!-- Set the X-Tenant-Code header to the tenantCode value from the context variable 'tenantinfo' -->
<set-header name="X-Tenant-Code" exists-action="override">
<value>@((string)((JObject)context.Variables["tenantinfo"]).GetValue("tenantCode"))</value>
</set-header>
<base />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
And that's it.
You can set up External Cache, from the following menu item.
APIM: External Cache |
Hope this helps.
Happy Coding.
Regards,
Jaliya
No comments:
Post a Comment