With Apim i'm trying to call a backend Api that needs a OAuth2 validation. This question are more or less similair to this: Azure API Management: Oauth2 with backend API
But there are no good answer here...
I have been reading alot about policies and caching.
But can't seem to set it up correctly. I hope to be able to cal the apim, and then the apim calls the backend api to get a token and with that token call an Api to get some output data.
I also found one where i had to setup some policies in the backend-part..
Can anyone help me set up the policies ?
my policy is like:
<policies>
<inbound>
<base />
<set-variable name="originBearer" value="#(context.Request.Headers.GetValueOrDefault("Authorization", "empty_token").Split(' ')[0].ToString())" />
<send-request ignore-error="true" timeout="20" response-variable-name="bearerToken" mode="new">
<set-url>{{lookupAccessTokenUrl}}</set-url>
<set-method>GET</set-method>
<set-header name="Content-Type" exists-action="override">
<value>application/x-www-form-urlencoded</value>
</set-header>
<set-body>#{
return "client_id={{HLR-app-client-id}}&scope={{HLR-scope}}&client_secret={{HLR-secret}}&assertion="+(string)context.Variables["originBearer"]+"&grant_type=urn:ietf:params:oauth:grant-type:client_credentials&requested_token_use=on_behalf_of";
}</set-body>
</send-request>
<set-variable name="requestResponseToken" value="#((String)((IResponse)context.Variables["bearerToken"]).Body.As<JObject>()["access_token"])" />
<set-header name="Authorization" exists-action="override">
<value>#("Bearer " + (string)context.Variables["requestResponseToken"])</value>
</set-header>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
I found the answer to my own Question :-)
I try to comment on each line, but if you take alle the code and put it together you get a policy to handle Oauth2 in a backend api.
In the inbound section, the cache-lookup-value
Assigns the value in cache to the context variable called “bearerToken”.
On first entry, the cache value will be null and the variable will not be
created.
<inbound>
<cache-lookup-value key="cacheAccessToken" variable-name="bearerToken" />
Create a variable that contains clientid and secret - needed to call the api
<set-variable name="user-password" value="{{HLR-Clientid}}:{{HLR-Secret}}"
/>
<choose>
Checks if the context variable collection contains a key called
“bearerToken” and if not found executes the code between the opening and closing
“” XML elements.
<when condition="#(!context.Variables.ContainsKey("bearerToken"))">
Initiates the request to the OAuth endpoint with a response
timeout of 20 seconds. This will put the response message into the variable
called “oauthResponse”
<send-request mode="new" response-variable-name="oauthResponse" timeout="20" ignore-error="false">
<set-url>{{lookupAccessTokenUrl}}</set-url>
<set-method>POST</set-method>
<set-header name="Content-Type" exists-action="override">
<value>application/x-www-form-urlencoded</value>
</set-header>
here you define your header Authorization and use the variable that contains clientid and password
<set-header name="Authorization" exists-action="override">
<value>#("Basic " + system.Convert.ToBase64String(Encoding.UTF8.GetBytes((string)context.Variables["user-password"])))</value>
</set-header>
<set-body>#("grant_type=client_credentials&scope={{HLR-Scope}}")</set-body>
</send-request>
Casts the response as a JSON object to allow the retrieval of the “access_token” value using an indexer and assigns it to the context variable “accessToken”.
<set-variable name="AccessToken" value="#((string)((IResponse)context.Variables["oauthResponse"]).Body.As<JObject>()["access_token"])" />
Store result in cache and where we add the contents of the variable “accessToken” into cache for a period of 3600 seconds.
<cache-store-value key="cacheAccessToken" value="#((string)context.Variables["AccessToken"])" duration="3600" />
Set the variable in a context-variable, then it can be used right now
<set-variable name="bearerToken" value="#((string)context.Variables["AccessToken"])" />
</when>
</choose>
<base />
</inbound>
<backend>
<!--Creates the request to the backend web service. Here we are placing the response from the web service into the variable called “transferWSResponse”.-->
<send-request mode="copy" response-variable-name="transferWSResponse" timeout="60" ignore-error="false">
<set-method>GET</set-method>
<!--Is the creating the “Authorization” header to be sent with the request.-->
<set-header name="Authorization" exists-action="override">
<value>#("Bearer " + (string)context.Variables["bearerToken"])</value>
</set-header>
<!--Removes the APIM subscription from being forwarded to the backend web service.-->
<set-header name="Ocp-Apim-Subscription-Key" exists-action="delete" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
</send-request>
</backend>
<outbound>
<!--Now we need to return the response message from the backend web service to the caller. This is done in the “<outbound>” policy section. Here we just simply return the value of the variable “transferWSResponse” back to the caller-->
<return-response response-variable-name="transferWSResponse" />
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
Related
I am trying to use Azure Pipeline to build my API Management infrastructure automatically and have successfully added the API and API Operation but having trouble defining the Operation specific Policy.
I have this policy that I based on a very useful article https://www.serverlessnotes.com/docs/expose-service-bus-queue-through-api-management
<policies>
<inbound>
<base />
<set-variable name="sasToken" value="#{
return "bob";
}" />
<set-header name="Authorization" exists-action="override">
<value>#(context.Variables.GetValueOrDefault<string>("sasToken"))</value>
</set-header>
<set-header name="Content-type" exists-action="override">
<value>application/json</value>
</set-header>
<set-header name="Ocp-Apim-Subscription-Key" exists-action="delete" />
<set-header name="BrokerProperties" exists-action="override">
<value>#{
return string.Format("{{\"SessionId\":\"{0}\"}}", "bob");
}</value>
</set-header>
<set-backend-service base-url="https://i365intfnapidevtbcoresb.servicebus.windows.net/i365intfnapidevtbcoresbqueue" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
There is actually more code in the value bits, but for illustration I've removed them.
However if I put this in a separate file (or even an inline variable) and run the Azure Powershell command
Set-AzApiManagementPolicy -Context $apim_context -ApiId $apiId -OperationId addmessage -PolicyFilePath <path to policy xml file>
Where the $ values are variables I declared previously.
I get the error
Error Details:
[Code= ValidationError, Message= 'bob' is an unexpected token. Expecting white space. Line 5, position 21., Target=
representation]
Basically, I cant work out how to format the function thats in the value attribute or value elements. The bit starting with #{}.
I can enter the policy via the Azure API Management screen no problem, but cant do it via the Set-AzApiManagementPolicy command.
Any ideas on how to format it.
Thanks.
It will escape it for you if you use the Format parameter.
Set-AzApiManagementPolicy -Context $apim_context -Format "application/vnd.ms-azure-apim.policy.raw+xml" -ApiId $apiId -OperationId addmessage -Policy $policyString
Solved it in the end, You have to encode some of the characters to make it work. For example #quot; instead of "
So I have in my Azure Powershell script:
$policyString = '
<policies>
<inbound>
<base />
<set-variable name="sasToken" value="#{
string resourceUri = "$(azure.servicebusqueueendpoint)";
string keyName = "$(azure.servicebusaccesspolicykey)";
string key = "$(azure.servicebusaccesspolicyvalue)";
TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + 120);
string stringToSign = System.Uri.EscapeDataString(resourceUri) + "\n" + expiry;
HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
var sasToken = String.Format("SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
System.Uri.EscapeDataString(resourceUri),
System.Uri.EscapeDataString(signature), expiry, keyName);
return sasToken;
}" />
<set-header name="Authorization" exists-action="override">
<value>#((string)context.Variables.GetValueOrDefault("sasToken"))</value>
</set-header>
<set-header name="Content-type" exists-action="override">
<value>application/json</value>
</set-header>
<set-header name="Ocp-Apim-Subscription-Key" exists-action="delete" />
<set-header name="BrokerProperties" exists-action="override">
<value>#{
return string.Format("{{\"SessionId\":\"{0}\"}}", "1");
}</value>
</set-header>
<set-backend-service base-url="$(azure.servicebusqueueendpoint)" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
'
Set-AzApiManagementPolicy -Context $apim_context -ApiId $apiId -OperationId addmessage -Policy $policyString
and this now seems to do the trick.
You would probably also need to do the same for c# things like:
context.Variables.GetValueOrDefault<string>
would be
context.Variables.GetValueOrDefault<string>
to make it work otherwise it conflicts with the xml
Thanks mclayton for pointing me in the right direction.
Trying to make a policy for getting a bearer token trough a send-request sticking it in the Authorization header and then posting JSON data to the given back-end.
But when I test it within the test tab of Azure I always receive the same error:
Even when I add <forward-request timeout="60" follow-redirects="60"/> it does not work.
I also tried it without the follow-redirects which is defaulted to false but also no effect.
I am completly new to Azure so any help would be appreciated.
Here is my policy:
<policies>
<inbound>
<base />
<send-request ignore-error="true" timeout="20" response-variable-name="bearerToken" mode="new">
<set-url>{{AuthenticationServer}}</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-header name="Authorization" exists-action="override">
<value>Basic {{Base64encodedusernamepassword}}</value>
</set-header>
<set-body>#{
return "grant_type=client_credentials";
}</set-body>
</send-request>
<set-header name="Authorization" exists-action="override">
<value>#("Bearer " + (String)((IResponse)context.Variables["bearerToken"]).Body.As<JObject>()["access_token"])</value>
</set-header>
<!-- Don't expose APIM subscription key to the backend. -->
<!--<set-header name="Ocp-Apim-Subscription-Key" exists-action="delete" /> -->
<set-backend-service base-url="{{BaseURI}}" />
</inbound>
<backend>
<forward-request timeout="60" follow-redirects="true" />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
The problem was that the endpoint was behind a private VNet so getting VPN access fixed the issue.
I am looking to implement an Azure API Management policy for bank account validation and as part of that API I want to call out to a token endpoint and pass that into the bank account validation. The problem I have is around setting the inbound send-request policy to accept the query parameters from NamedValues/KeyVault.
The URL for the token validation is as below:
https://apps.applyfinancial.co.uk/validate-api/rest/authenticate?username=USERNAME.com&password=PASSWORD
I tried using the set-query-parameter policy but it appears that this is not allowed within the send-request node based on the below validation error:
Error in element 'send-request' on line 16, column 10: The element
'send-request' has invalid child element 'set-query-parameter'. List
of possible elements expected: 'set-header, set-body,
authentication-certificate, authentication-token,
authentication-token-store, authentication-managed-identity, proxy'.
One or more fields contain incorrect values:;Error in element
'send-request' on line 16, column 10: The element 'send-request' has
invalid child element 'set-query-parameter'. List of possible elements
expected: 'set-header, set-body, authentication-certificate,
authentication-token, authentication-token-store,
authentication-managed-identity, proxy'.
POLICY
<policies>
<inbound>
<!-- 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://apps.applyfinancial.co.uk/validate-api/rest/authenticate</set-url>
<set-method>POST</set-method>
<set-query-parameter name="username" exists-action="override">
<value>{{BankValidationUsername}}</value>
</set-query-parameter>
<set-query-parameter name="password" exists-action="override">
<value>{{BankValidationPassword}}</value>
</set-query-parameter>
</send-request>
<base />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
My question is how do you set query parameters in the send-request section of an API policy?
OK,
You can't set a query parameter within the scope of the send-request but you can do it within the ionbound policy. Also it seems better to pull the KeyVault hosted Named Values in to variables and use them in the request that way.
<policies>
<inbound>
<rewrite-uri template="/" />
<set-variable name="username" value="{{BankValidationUsername}}" />
<set-variable name="password" value="{{BankValidationPassword}}" />
<set-variable name="errorresponse" value="" />
<send-request mode="new" response-variable-name="tokenstate" ignore-error="false">
<set-url>#($"https://apps.applyfinancial.co.uk/validate-api/rest/authenticate?username={(string)context.Variables["username"]}&password={(string)context.Variables["password"]}")</set-url>
<set-method>POST</set-method>
</send-request>
<set-query-parameter name="token" exists-action="override">
<value>#((string)((IResponse)context.Variables["tokenstate"]).Body.As<JObject>()["token"])</value>
</set-query-parameter>
<base />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<set-header name="ErrorSource" exists-action="override">
<value>#(context.LastError.Source)</value>
</set-header>
<set-header name="ErrorReason" exists-action="override">
<value>#(context.LastError.Reason)</value>
</set-header>
<set-header name="ErrorMessage" exists-action="override">
<value>#(context.LastError.Message)</value>
</set-header>
<set-header name="ErrorScope" exists-action="override">
<value>#(context.LastError.Scope)</value>
</set-header>
<set-header name="ErrorSection" exists-action="override">
<value>#(context.LastError.Section)</value>
</set-header>
<set-header name="ErrorPath" exists-action="override">
<value>#(context.LastError.Path)</value>
</set-header>
<set-header name="ErrorPolicyId" exists-action="override">
<value>#(context.LastError.PolicyId)</value>
</set-header>
<set-header name="ErrorStatusCode" exists-action="override">
<value>#(context.Response.StatusCode.ToString())</value>
</set-header>
<base />
</on-error>
</policies>
I'm configuring inbound policies in an instance of Azure API Management.
First, I set a variable:
<set-variable name="var1" value="" />
Then I send a request
<send-request mode="new" response-variable-name="var1" timeout="20" ignore-error="false">
Which returns a JSON. When testing I get the following message in trace tab:
GET request to 'https://my-api.azure-api.net/api/data' has been sent, result stored in 'var1' variable.
I guess the send-request policy works and the result is stored in the variable.
Then I want to return a response (still in inbound, I get 500 when trying to do it in outbound):
<return-response response-variable-name="existing response variable">
<set-status code="200" reason="OK" />
<set-header name="Content-Type" exists-action="override">
<value>application/json</value>
</set-header>
<set-body>
{
"success": true,
"var1": context.Variables["var1"]
}
</set-body>
</return-response>
My problem is it doesn't work... It just renders context.Variables["var1"].
And so does:
#context.Variables["var1"]
#{ context.Variables.GetValueOrDefault<string>("var1") }
#context.Variables.GetValueOrDefault("var1")
All of them are rendered as written, no value is being extracted.
Edit: I also tried adding a placeholder string and then using
<find-and-replace from="Placeholder" to="context.Variables.GetValueOrDefault("var1")" />
And try to place it in inbound and outbound alike. But this policy did not launch.
It's a JSON object that I want to append to the response (small detail: in reality I have this issue with multiple variables).
My question is: how can I add my declared variable to the response?
There are two ways you can go about this. You could to use policy expressions for that: https://learn.microsoft.com/en-us/azure/api-management/api-management-policy-expressions. The thing to remember is that they can only be used to construct whole value for policy, not part of it, so:
<set-body>#("{\"success\": true, \"var1\": " + ((IResponse)context.Variables["var1"]).Body.As<string>() + "}"</set-body>
Or with set-body policy you could use liquid template:
<set-variable name="var1body" value="#((IResponse)context.Variables["var1"]).Body.As<string>())" />
<set-body template="liquid">
{
"success": true,
"var1": {{context.Variables["var1body"]}}
}
</set-body>
I assume you have some sidecar request going on additionally to your main request flow.
This sample adds the response from send-request to the response body of the main request:
<policies>
<inbound>
<base />
<!-- main request -->
<set-backend-service base-url="https://reqres.in" />
<rewrite-uri template="/api/users/2" />
<!-- sidecar request -->
<send-request mode="new" response-variable-name="var1" timeout="20" ignore-error="true">
<set-url>https://reqres.in/api/unkown/2</set-url>
<set-method>GET</set-method>
</send-request>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
<set-body template="none">#{
var body = context.Response.Body.As<JObject>(true);
body["var1"] = ((IResponse)context.Variables["var1"]).Body.As<JObject>();
return body.ToString();
}</set-body>
</outbound>
<on-error>
<base />
</on-error>
</policies>
Using policies in Azure API Management, I'm trying to rename a query parameter, but it does not work. If I change copy-unmatched-params="false" to copy-unmatched-params="true" then it works, but the behaviour becomes that all unmatched parameters will get passed to the backend API which would allow clients to inject their own query params into the backend request which we do not want.
Everything else is fine.
I want to transform a request that comes in that looks like this:
https://{site}/search?query=dylan
To:
https://{backend-site}documents?api-version=2018-1-11&amount=1000&searchFields=Album,Artist&search=dylan
The only part that doesn't work is transforming the query parameter to be named "search" instead of query without allowing all parameters to be passed on from the inbound querystring. How can I fix that?
<policies>
<inbound>
<rewrite-uri template="/" copy-unmatched-params="false" />
<set-header name="api-key" exists-action="override">
<value>THIS-IS-API-KEI</value>
</set-header>
<set-query-parameter name="api-version" exists-action="override">
<value>2018-1-11</value>
</set-query-parameter>
<set-query-parameter name="amount" exists-action="override">
<value>1000</value>
</set-query-parameter>
<set-query-parameter name="searchFields" exists-action="override">
<value>Album,Artist</value>
</set-query-parameter>
<set-query-parameter name="search" exists-action="override">
<value>#(context.Request.Url.Query.GetValueOrDefault("query"))</value>
</set-query-parameter>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
The reason why you're getting empty value from your last expression is because by that time your URI already rewritten to "/" and only "api-version", "amount", and "searchFields" query parameters are set. There are a few ways to go about that:
Refer to original Url: #(context.Request.OriginalUrl.Query.GetValueOrDefault("query"))
Add query to operation URI template - search?query={query} - and refer it from rewrite-uri policy: <rewrite-uri template="/?query={query}" copy-unmatched-params="false" />. The downside is that "query" parameter becomes required, so any request without it will result in 404.
Try using variables. Assign the value to variable in the beginning and use the variable for assigning new query parameter
You may rename parameter in the query by using string of the URL and simple replace method. In that case, the parameter wouldn't be mandatory.
<inbound>
<base />
<rewrite-uri template="#{
return "/some-url-here-or-your-previously-constructed-url" + context.Request.OriginalUrl.QueryString
.Replace("old-name", "new-name");
}" copy-unmatched-params="false" />
</inbound>