I have the below policy in Azure API Management. It works fine like that:
<return-response>
<set-status code="302" />
<set-header name="Location" exists-action="override">
<value>#{
try
{
// ... doing something here that might throw an Exception
return "http://example.com";
}
catch (Exception e)
{
// If something failed, it is usually because of an transient error. Then we just send the user to the same URL again to retry.
return "/";
}
}</value>
</set-header>
</return-response>
Now I would like to log/trace a message inside the Catch block. Basically something like this, but this of course does not work.
catch (Exception e)
{
<trace source="MyApi" severity="error">
<message>Error foo</message>
<metadata name="ErrorMessage" value="#(e.Message)"/>
</trace>
return "/";
}
How can I do this or how otherwise send logs to the connected Application Insights instance?
You let <on-error> section handle the tracing:
<policies>
<inbound>
<base />
<return-response>
<set-status code="302" />
<set-header name="Location" exists-action="override">
<value>#{
int d = 0;
int result = 42 / d;
return result.ToString();
}</value>
</set-header>
</return-response>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
<trace source="ErrorInformation" severity="error">
<message>ErrorInformation</message>
<metadata name="EsbCorrelationId" value="#((!context.Variables.ContainsKey("esb-correlation-id") || context.Variables.GetValueOrDefault<string>("esb-correlation-id").Equals(string.Empty)) ? "-none-" : context.Variables.GetValueOrDefault<string>("esb-correlation-id"))" />
<metadata name="IpAddress" value="#(context.Request.IpAddress.ToString())" />
<metadata name="LastError.Source" value="#((context.LastError.Source == null || context.LastError.Source.Equals(string.Empty)) ? "-none-" : context.LastError.Source.ToString())" />
<metadata name="LastError.Reason" value="#((context.LastError.Reason == null || context.LastError.Reason.Equals(string.Empty)) ? "-none-" : context.LastError.Reason.ToString())" />
<metadata name="LastError.Message" value="#((context.LastError.Message == null || context.LastError.Message.Equals(string.Empty)) ? "-none-" : context.LastError.Message.ToString())" />
<metadata name="LastError.Scope" value="#((context.LastError.Scope == null || context.LastError.Scope.Equals(string.Empty)) ? "-none-" : context.LastError.Scope.ToString())" />
<metadata name="LastError.Section" value="#((context.LastError.Section == null || context.LastError.Section.Equals(string.Empty)) ? "-none-" : context.LastError.Section.ToString())" />
<metadata name="LastError.Path" value="#((context.LastError.Path == null || context.LastError.Path.Equals(string.Empty)) ? "-none-" : context.LastError.Path.ToString())" />
<metadata name="LastError.PolicyId" value="#((context.LastError.PolicyId == null || context.LastError.PolicyId.Equals(string.Empty)) ? "-none-" : context.LastError.PolicyId.ToString())" />
</trace>
</on-error>
</policies>
which will give you an entry in traces with this content in customDimensions:
LastError.Message
Expression evaluation failed. Attempted to divide by zero.
LastError.Path
return-response
LastError.PolicyId
-none-
LastError.Reason
ExpressionValueEvaluationFailure
LastError.Scope
operation
LastError.Section
inbound
LastError.Source
set-header
Related
I try to check request body like this.
<set-variable name="isBody" value="#{
bool rtn_result = false;
if (context.Request.Body == null)
{
rtn_result = true;
}
return rtn_result;
}" />
but 'rtn_result' return false always
I followed the document and they told me
"context.Request Body: IMessageBody or null if request doesn't have a body."
What I mistake?
What is the right thing?
What should I do?
Thx.
Your if condition does not make sense.
It returns true if there's no body.
Please change it to:
if (context.Request.Body != null)
{
rtn_result = true;
}
Complete policy:
<policies>
<inbound>
<base />
<set-variable name="isBody" value="#{
bool rtn_result = false;
if (context.Request.Body != null)
{
rtn_result = true;
}
return rtn_result;
}" />
<return-response>
<set-status code="200" reason="OK" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>#{
var response = new JObject();
response["hasBody"] = context.Variables.GetValueOrDefault<bool>("isBody");
return response.ToString();
}</set-body>
</return-response>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Result with body:
Result without body:
I require some help on doing a rewrite uri policy. I was able to extract information from payload and change the initial request URL. Have extracted the insurer_id from body and passed it to my backend -> mybackend.com/api/relationship/deny/{insurer_id}
I was able to extract it and send it to my backend, I have a bit of a trouble formatting once the request has been posted
I have a current payload:
{
"insurer_id": "22112",
"insurer_name": "Steve Rogers",
"status_code: [ " "Deny\",",
],
"additionalComments": "This is a test"
}
This is my Current code:
<policies>
<inbound>
<base />
<set-variable name="insurerId" value="#{
var body = context.Request.Body.As<JObject>(true);
return body["insurer_id"].Value<string>();
}" />
<set-variable name="status_code" value="#{
return context.Request.Body.As<JObject>(preserveContent:true)["status_code"].ToString(Newtonsoft.Json.Formatting.None);
}" />
<rewrite-uri template="#("/api/relationship/deny/" + context.Variables.GetValueOrDefault<string>("insurerId") + "?denyReason=" + context.Variables.GetValueOrDefault<string>("status_code"))" copy-unmatched-params="false" />
<set-backend-service base-url="https://testbackend.azure-api.net" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
What I am trying to achieve works, but when I do a post, the payload doesn't look what is to be expected.
Expected Payload:
{
"insurer_id": "22112",
"insurer_name": "Steve Rogers",
"status_code": "Deny",
"additionalComments": "This is a test"
}
Unformatted payload:
{
"insurer_id": "22112",
"insurer_name": "Steve Rogers",
"status_code": "[\"Deny\"]",
"additionalComments": "This is a test"
}
You have to read the first value of your JArray and assign it to the variable:
<set-variable name="status_code" value="#{
var body = context.Request.Body.As<JObject>(true);
var statusCode = body["status_code"] as JArray;
if(statusCode != null && statusCode.Count > 0)
{
return statusCode.FirstOrDefault();
}
return null;
}" />
But therefore you have to fix your invalid JSON:
{
"insurer_id": "22112",
"insurer_name": "Steve Rogers",
"status_code": [ "Deny"],
"additionalComments": "This is a test"
}
A query string consists of a key-value pair:
https://example.com/over/there?name=ferret
name is the key
ferret is the value for it
In your example, there's no key:
? + denyReason=context.Variables.GetValueOrDefault<string>("status_code")
and also the ? is a string. So it has to be put between quotation marks.
Fixed with sample query-key status_code:
<rewrite-uri template="#("/api/relationship/deny/" + context.Variables.GetValueOrDefault<string>("insurerId") + "?statuscode=" + context.Variables.GetValueOrDefault<string>("status_code"))" copy-unmatched-params="false" />
Complete policy:
<policies>
<inbound>
<base />
<set-variable name="insurerId" value="#{
var body = context.Request.Body.As<JObject>(true);
return body["insurer_id"].Value<string>();
}" />
<set-variable name="status_code" value="#{
var body = context.Request.Body.As<JObject>(true);
var statusCode = body["status_code"] as JArray;
if(statusCode != null && statusCode.Count > 0)
{
return statusCode.FirstOrDefault();
}
return null;
}" />
<rewrite-uri template="#("/api/relationship/deny/" + context.Variables.GetValueOrDefault<string>("insurerId") + "?statuscode=" + context.Variables.GetValueOrDefault<string>("status_code"))" copy-unmatched-params="false" />
<set-backend-service base-url="https://testbackend.azure-api.net" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
I am trying to check if a param exists in the body of the POST request.
But i get the following error :
{
"messages": [
{
"message": "Expression evaluation failed.",
"expression": "(string)context.Variables[\"param1\"] != null || ((string)context.Variables[\"param2\"] != null) || ((string)context.Variables[\"param3\"] != null) || ((string)context.Variables[\"param4\"] != null)",
"details": "Unable to cast object of type 'Newtonsoft.Json.Linq.JValue' to type 'System.String'."
},
"Expression evaluation failed. Unable to cast object of type 'Newtonsoft.Json.Linq.JValue' to type 'System.String'.",
"Unable to cast object of type 'Newtonsoft.Json.Linq.JValue' to type 'System.String'."
]
}
I define values like this :
<set-variable name="param1" value="#(context.Request.Body.As<JValue>(preserveContent: true).GetValueOrDefault("param1", null))" />
<set-variable name="param2" value="#(context.Request.Body.As<JValue>(preserveContent: true).GetValueOrDefault("param2", null))" />
<set-variable name="param3" value="#(context.Request.Body.As<JValue>(preserveContent: true).GetValueOrDefault("param3", null))" />
<set-variable name="param4" value="#(context.Request.Body.As<JValue>(preserveContent: true).GetValueOrDefault("param4", null))" />
This is my test code :
<when condition="#((string)context.Variables["param"] != null || ((string)context.Variables["param2"] != null)
|| ((string)context.Variables["param3"] != null) || ((string)context.Variables["param4"] != null))">
...
</when>
This is what I send in the post body :
{"param2":false}
I also tried with
context.Request.Body.As<JObject>
but I got the same error.
And also with just :
<set-variable name="param1" value="#(context.Request.Body.As<JObject>(preserveContent: true).GetValue("param1"))" />
What should I do?
This solution works for following request body:
{
"param2": "",
"param3": null,
"param4": "lorem"
}
The policy checks if a parameter exists, is null or is empty.
In case, the validation fails, a message will be set and returned.
<policies>
<inbound>
<base />
<choose>
<when condition="#(context.Request.Body.As<JObject>(true)["param1"] == null || context.Request.Body.As<JObject>(true)["param1"].Type == JTokenType.Null || string.IsNullOrEmpty(context.Request.Body.As<JObject>(true)["param1"].Value<string>()) )">
<set-variable name="message" value="#(context.Variables.GetValueOrDefault<string>("message") + "param1 missing! ")" />
</when>
</choose>
<choose>
<when condition="#(context.Request.Body.As<JObject>(true)["param2"] == null || context.Request.Body.As<JObject>(true)["param2"].Type == JTokenType.Null || string.IsNullOrEmpty(context.Request.Body.As<JObject>(true)["param2"].Value<string>()) )">
<set-variable name="message" value="#(context.Variables.GetValueOrDefault<string>("message") + "param2 missing! ")" />
</when>
</choose>
<choose>
<when condition="#(context.Request.Body.As<JObject>(true)["param3"] == null || context.Request.Body.As<JObject>(true)["param3"].Type == JTokenType.Null || string.IsNullOrEmpty(context.Request.Body.As<JObject>(true)["param3"].Value<string>()) )">
<set-variable name="message" value="#(context.Variables.GetValueOrDefault<string>("message") + "param3 missing! ")" />
</when>
</choose>
<choose>
<when condition="#(context.Request.Body.As<JObject>(true)["param4"] == null || context.Request.Body.As<JObject>(true)["param4"].Type == JTokenType.Null || string.IsNullOrEmpty(context.Request.Body.As<JObject>(true)["param4"].Value<string>()) )">
<set-variable name="message" value="#(context.Variables.GetValueOrDefault<string>("message") + "param4 missing! ")" />
</when>
</choose>
<choose>
<when condition="#(!String.IsNullOrEmpty(context.Variables.GetValueOrDefault<string>("message")))">
<return-response>
<set-status code="400" reason="Bad Request" />
<set-body>#{
return context.Variables.GetValueOrDefault<string>("message");
}</set-body>
</return-response>
</when>
</choose>
<return-response>
<set-status code="200" reason="OK" />
<set-body>ok</set-body>
</return-response>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Sample with Postman:
Different solution
API Management has also validation policies.
By defining the OpenApi schema with required, pattern,..., the policy is able to validate it.
OpenApi:
paths:
/validate2:
post:
summary: Validate2
description: Validate2
operationId: validate2
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Request'
example:
param1: string
param2: string
param3: string
param4: string
responses:
'200':
description: OK
components:
schemas:
Request:
required:
- param1
- param2
- param3
- param4
type: object
properties:
param1:
type: string
param2:
type: string
param3:
type: string
param4:
type: string
Policy:
<policies>
<inbound>
<base />
<validate-content unspecified-content-type-action="prevent" max-size="102400" size-exceeded-action="ignore" errors-variable-name="validateContent">
<content type="application/json" validate-as="json" action="prevent" />
</validate-content>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Response:
{
"statusCode": 400,
"message": "Body of the request does not conform to the definition which is associated with the content type application/json. Invalid type. Expected String but got Null. Line: 3, Position: 18"
}
I have an API management inbound policy where I can grab a users id from within a JWT. For the purpose of some testing, I then want API management to check in the policy, "Is this ID within the list of tester IDs that are allowed to access here"
<policies>
<inbound>
<base />
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid.">
<openid-config url="myurl" />
</validate-jwt>
<set-header name="header-value-userId" exists-action="override">
<value>#(context.Request.Headers.GetValueOrDefault("Authorization").AsJwt()?.Claims.GetValueOrDefault("oid"))</value>
</set-header>
<!--
PSEUDOCDOE below to describe my intention
How can I check that the abover header-value-userId value is within a hardcoded list at this point?
#{
string[] userList = ["user1", "user2", "user3"];
var match = userList.FirstOrDefault(x => x.Contains(header-value-userId));
if(match == null)
return bad request
}
-->
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Thanks for any help.
--- UPDATE ---
Thanks to the answer from Markus Meyer I now have this working. See below my now full working example.
<policies>
<inbound>
<base />
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid.">
<openid-config url="myurl" />
</validate-jwt>
<set-variable name="isValidUser" value="#{
var email = context.Request.Headers.GetValueOrDefault("Authorization").AsJwt()?.Claims.GetValueOrDefault("emails");
string[] emailList = new string[] { "email1#gmail.com", "email2#gmail.com" };
var match = emailList.Any(x => x.Contains(email));
if(match == true)
{
return true;
}
return false;
}" />
<choose>
<when condition="#(context.Variables.GetValueOrDefault<bool>("isValidUser") == false)">
<return-response>
<set-status code="401" reason="Unauthorized" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>#("{\"status\": \"" + "User not valid" + "\"}")</set-body>
</return-response>
</when>
<otherwise>
<return-response>
<set-status code="200" reason="Valid" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>#("{\"status\": \"" + "User valid" + "\"}")</set-body>
</return-response>
</otherwise>
</choose>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Using the choose policy allows you to check a boolean condition.
In the current example, the condition is set in the set-variable policy
return-response will return your wanted response.
Complete example:
<policies>
<inbound>
<base />
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid.">
<openid-config url="myurl" />
</validate-jwt>
<set-header name="header-value-userId" exists-action="override">
<value>#(context.Request.Headers.GetValueOrDefault("Authorization").AsJwt()?.Claims.GetValueOrDefault("oid"))</value>
</set-header>
<set-variable name="isValidUser" value="#{
string[] userList = new string[] { "user1", "user2", "user3" };
var match = userList.FirstOrDefault(x => x.Contains("header-value-userId"));
if(match == null)
{
return true;
}
return false;
}" />
<choose>
<when condition="#(context.Variables.GetValueOrDefault<bool>("isValidUser") == false)">
<return-response>
<set-status code="401" reason="Unauthorized" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>#("{\"status\": \"" + "User not valid" + "\"}")</set-body>
</return-response>
</when>
</choose>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
A few things to note:
This this the correct code for the string[]:
string[] userList = new string[] { "user1", "user2", "user3" };
There's no need to store the usereId into the header. The value can also be stored in a variable.
Is there any way to decrypt a bearer token in an API management policy in order to create a condition it's acr_values, for example a tenant.
Looking at the MS documentation it does not seem possible, I would be looking to achieve something like:
<when condition="#(context.Request.Headers["Authorization"] --DO MAGIC HERE-- .acr_values["tenant"] == "contoso" ">
<set-backend-service base-url="http://contoso.com/api/8.2/" />
</when>
Alternatively something like the example here but for setting the backed service:
http://devjourney.com/blog/2017/03/23/extract-jwt-claims-in-azure-api-management-policy/
Documentation I've read:
https://learn.microsoft.com/en-us/azure/api-management/api-management-transformation-policies#example-4
https://learn.microsoft.com/en-us/azure/api-management/policies/authorize-request-based-on-jwt-claims?toc=api-management/toc.json#policy
Did you try .AsJwt() method (https://learn.microsoft.com/en-us/azure/api-management/api-management-policy-expressions#ContextVariables):
<policies>
<inbound>
<base />
<set-header name="tenant" exists-action="append">
<value>#{
var jwt = context.Request.Headers.GetValueOrDefault("Authorization").AsJwt();
return jwt?.Claims.GetValueOrDefault("tenant") ?? "unknown";
}</value>
</set-header>
<choose>
<when condition="#(context.Request.Headers.GetValueOrDefault("tenant", "unknown") == "some-tenant" )">
<set-backend-service base-url="http://contoso.com/api/8.2/" />
</when>
</choose>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
Also I'm not sure if you need it as a header to backend request, if not consider using set-variable policy.
Ok so I got it working in a very hacky way, you can set vales of the decrypted token in the header and then set conditions on that header.
<policies>
<inbound>
<base />
<set-header name="tenant" exists-action="append">
<value>#{
string tenant = "unknown";
string authHeader = context.Request.Headers.GetValueOrDefault("Authorization", "");
if (authHeader?.Length > 0)
{
string[] authHeaderParts = authHeader.Split(' ');
if (authHeaderParts?.Length == 2 && authHeaderParts[0].Equals("Bearer", StringComparison.InvariantCultureIgnoreCase))
{
Jwt jwt;
if (authHeaderParts[1].TryParseJwt(out jwt))
{
tenant = (jwt.Claims.GetValueOrDefault("tenant", "unknown"));
}
}
}
return tenant;
}</value>
</set-header>
<choose>
<when condition="#(context.Request.Headers.GetValueOrDefault("tenant", "unknown") == "some-tenant" )">
<set-backend-service base-url="http://contoso.com/api/8.2/" />
</when>
</choose>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
A few years have passed since this has been answered, but as I found a less verbose solution, without actually modifying the request headers, i thought it would be nice to share for others:
<set-variable name="tenant" value="#{
var authHeader = context.Request.Headers.GetValueOrDefault("Authorization", "");
return authHeader.AsJwt()?.Claims.GetValueOrDefault("tenant", "");
}" />
...
<choose>
<when condition="#(context.Variables.GetValueOrDefault("tenant", "") == "your-tenant-id")">