Azure API Management (APIM) is Microsoft’s fully managed service that enables organizations to publish, secure, transform, and monitor APIs. The service features multiple types of monitoring and logging. It’s part of the Azure Integration Service suite of offerings, including centrally managed messaging services like Service Bus, Event Grid (Event Bus), and integration with Logic Apps.
API Gateway
APIM is an API Gateway like its competitors, Google Apigee API Management, IBM API Connect, and Axway API Gateway. The service acts as an API front-end, receiving API requests, enforcing throttling and security policies, passing requests to the back-end service, and then passing the response back to the requester. It is a central entry point for managing and securing APIs, simplifying client access to various services, and enhancing overall API functionality.
In the context of APIM, the backend service can be a Logic App, Azure Function, or Container App. It can also be a service bus queue or topic or an Event Grid topic. In scenarios to persist a message (request) on a queue or topic for further processing or an event handled further down in a process, APIM can be an option to secure the endpoint of a given Service Bus queue or topic or a customer Event Grid topic.
This blog post will dive into persisting a message in an Azure Service Bus queue for further processing.
Protect Service Bus Queue Endpoint
In APIM, you can create an API with an operation tied to a service bus endpoint. To successfully post a message to a queue via APIM, there are several steps to follow. The first is creating a service namespace and queue. Secondly, create a Shared Access Signature to provide access to the queue. Next, you create an API with an operation with the POST method and a policy that includes the address of the queue (endpoint), queue name, authorization, and brokered properties.
The policy for the POST operation to send messages to a service bus queue (back-end) could look like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<policies> <inbound> <base /> <set-header name="Authorization" exists-action="override"> <value>{{SAS}}</value> </set-header> <set-header name="Content-Type" exists-action="override"> <value>vnd.microsoft.servicebus.yml</value> </set-header> <set-backend-service base-url="https://demo-messages.servicebus.windows.net" /> <set-header name="Ocp-Apim-Subscription-Key" exists-action="delete" /> <set-header name="BrokerProperties" exists-action="override"> <value>@{ var json = new JObject(); json.Add("MessageId", context.RequestId); json.Add("Label", "TestData"); return json.ToString(Newtonsoft.Json.Formatting.None); }</value> </set-header> </inbound> <backend> <base /> </backend> <outbound> <base /> </outbound> <on-error> <base /> </on-error> </policies> |
The value for Authorization (reference SAS) is set in the Named Values of APIM and contains a value like:
SharedAccessSignature sr=https%3a%2f%2fsb-xxx-yyyyyy-d.servicebus.windows.net
&sig=ij1fppXFmbvUpQ%2fX%2f8cRL%2f5A3FAcQQI6zgxNlPdxysU%3d&se=2914739250&skn=sendmessages
To generate the value, you can leverage the following .NET Code:
1 2 3 4 5 6 7 8 9 10 11 12 |
TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1); var year = 60 * 60 * 24 * 7 * 365; var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + year); string stringToSign = HttpUtility.UrlEncode("https://demo-messages.servicebus.windows.net") + "\n" + expiry; HMACSHA256 hmac = new HMACSHA256(Encoding.UTF8.GetBytes("zMvTTi4E97KllELnPqB6p96mMRUE0AxzR+ASbLq2AUY=")); var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign))); var sasToken = String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}", HttpUtility.UrlEncode("https://demo-messages.servicebus.windows.net"), HttpUtility.UrlEncode(signature), expiry, "sendmessages"); |
As an alternative, you can use a PowerShell script or through the policy. The time span is part you need to consider here – how long do you want to allow the SAS to be used? The example above shows a value for the duration of a year. Storing the key in Azure Key Vault is a good practice, and on top of that, apply automation of the key.
Note that you can add BrokeredProperties on the message through the policy, and you need to add the rewrite URL for ending the endpoint to the queue with /messages. Furthermore, the headers are passed along as custom properties (such as Ocp-Apim-Subscription-Key), which you can remove through the policy.
You can test if a message is persisted on a queue using Postman:

And verify if the message is on the queue:

The queue is protected with an API in APIM, and you can further enhance security by setting up authentication to call the API through OAuth 2.0. Another option involves isolation by placing APIM and the Service Bus namespace in a VNET with the proper network segmentation, etc. However, that increases cost, complexity, and stricter governance. The solution described in this blog post does mean the APIM and the Service Bus are accessible through the public internet. Yet, authentication/authorization can be set up in APIM, and for access to the queue, you use a policy with a SAS key that has an expiration and is rotated.
User Assigned Managed Identity
Alternatively, to the SAS key example in this blog post and described in others is that you can create a User-Assigned Managed Identity, add that identity in APIM through the Managed Identity Tab, and in the Service Bus under IAM assign that identity to the Service Bus Builtin Role: Azure Service Bus Data Sender.
APIM:

Service Bus:

The policy in APIM changes to:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<policies> <inbound> <base /> <set-backend-service base-url="https://demo-messages.servicebus.windows.net" /> <authentication-managed-identity resource="https://demo-messages.servicebus.windows.net" client-id="hhhddf54-a53c-4b29-a2bb-2136ee9075d3" /> <rewrite-uri template="/my-queue/messages" /> <set-header name="Ocp-Apim-Subscription-Key" exists-action="delete" /> <set-header name="BrokerProperties" exists-action="override"> <value>@{ var json = new JObject(); json.Add("MessageId", context.RequestId); json.Add("Label", "TestData"); return json.ToString(Newtonsoft.Json.Formatting.None); }</value> </set-header> </inbound> <backend> <base /> </backend> <outbound> <base /> </outbound> <on-error> <base /> </on-error> </policies> |
Conclusion
A potential use case for the described solution can be ingesting messages to cache them for further processing, providing durability, and protecting the back-end system. There is no direct access to the back end when it picks up messages from the queue; it can pick them up at the desired pace. To protect the queue besides the mentioned security, you can apply a rate-limiting policy or change the format from JSON to XML if necessary.
APIM, in the end, provides you with flexibility through policies and the ability to protect any endpoint, including a queue or what we will discuss in the next blog post, an Event Grid topic.