API Management with a Logic Apps Backend

I’ve been exploring ways to build secure, scalable backend services in Azure, and I recently came across a feature that allows using Microsoft Logic Apps as a backend service for Azure API Management. This caught my attention because it offers a practical way to expose workflows as APIs without having to manage complex backend infrastructure.

In this post, I’m documenting my attempt to set up a Logic App as a backend service for a simple desktop application I’ve been working on. The idea is to route API requests from my application through Azure API Management, which will handle communication with the Logic App.

I’m particularly interested in improving security by moving away from traditional SAS tokens and implementing managed identities and OAuth 2.0 authentication. My goal is to ensure that the backend service is both secure and easy to maintain, without exposing any sensitive information directly to the API consumers.

I’ll also share what worked, what didn’t, and any challenges I faced along the way. This post should help anyone trying to build something similar in Azure, especially if you want to secure your backend services without relying on static secrets.

Prerequisites

Before diving into the setup, there are a few things I had to get in place. These prerequisites will help ensure you can follow along without hitting any unexpected roadblocks. Here’s what I started with:

  • Azure Subscription
    I used an active Azure subscription for all the resources. If you don’t have one, you can sign up for a free trial to get started.
  • Azure API Management Instance (APIM)
    I already had an API Management instance (Developer Tier) created. If you’re setting this up from scratch, make sure to choose a suitable tier that supports managed identities and backend service integration.
  • Azure Logic App (Standard Workflow)
    For this setup, I used a Logic App Standard Workflow instead of a Consumption Logic App. The Standard SKU provides better flexibility and managed identity support, which is critical for securing the backend service.
  • Desktop Application (Optional)
    I’m using a .NET-based desktop application to make API calls. You can test the APIs using tools like Postman, but I wanted to see how the flow would work in a real application.
  • Basic Understanding of Azure Managed Identities
    Managed identities allow Azure resources to authenticate without storing credentials. I used both system-assigned and user-assigned managed identities to secure communication between APIM and the Logic App.

Important Notes

  • As of 2022, API Management supports automated import of a Logic App (Consumption) resource. For this reason we will not be using the Logic App Import Wizard. 
  • OAuth 2.0 + OpenID Connect configuration in the APIM portal is primarily intended to: Secure the Developer Portal login and Enable OAuth-based authentication flows for API consumers (e.g., when API users log in to get an access token). It’s NOT intended to authenticate APIM when calling backend services (like Logic Apps).

Architecture Overview

Before jumping into the setup, I want to outline the overall architecture of what I’m building. Having a clear picture of how everything connects helped me stay organised, so I’m sharing that here as well.

The flow looks like this:

Desktop App → Azure API Management (APIM) → Azure Logic App (Backend Service)

Here’s a quick breakdown of each component and its role:

  • Azure Managed Identity
    Security is a big focus of this setup. Instead of using static keys or tokens, I’m relying on managed identities to authenticate the connection between APIM and the Logic App. This ensures that no sensitive credentials are exposed in the request.
  • Desktop Application
    This is the client making API requests. In my case, it’s a .NET app that needs to interact with the backend to retrieve or update product data.
  • Azure API Management (APIM)
    APIM acts as the front door for all API requests. It abstracts the backend Logic App URL, handles authentication, and ensures requests are processed securely. I’m using the Developer Tier, which is cost-effective for testing purposes.
  • Azure Logic App (Standard Workflow)
    The Logic App serves as the backend service. It has two operations – GetProducts (to retrieve product data) and PostProducts (to add or update product data). These workflows are triggered by HTTP requests.

Why This Architecture?

I chose this architecture because it strikes a balance between simplicity and security:

  • It allows me to abstract the Logic App endpoint from the client application.
  • Using managed identities means I don’t have to deal with rotating keys or storing sensitive information.
  • It’s scalable and can be extended to more complex workflows if needed.

In short, this setup helps me achieve a secure, maintainable backend service for my app while leveraging Azure’s built-in security features.

In the next section, I’ll walk you through the basic setup, starting with creating the Logic App backend.

Basic Integration Setup

I started with a simple integration to get things up and running quickly before adding any authentication or security layers. This helped me understand how the pieces fit together without overcomplicating things right away.

Here’s the plan for the basic setup:

  • Create a Logic App (Standard Workflow) to act as the backend service.
  • Set up Azure API Management to expose the Logic App as an API.
  • Test the integration using a public endpoint before securing it with managed identities.

Creating the Logic App Backend

I began by creating a new Logic App (Standard) in the Azure Portal. I chose the Standard SKU because it offers more flexibility and supports managed identities, which I’d need later.

Here’s how I set it up:

  • Name: exp-backend01
  • SKU: Standard
  • Region: West Europe (you can choose any region you prefer)

Once the Logic App was created, I added two simple operations to handle API requests:

  • GET: GetProducts
    This operation retrieves a list of products.
  • POST: PostProducts
    This operation adds or updates a product.

Both operations use a basic HTTP trigger. At this point, I left the Logic App unsecured, meaning anyone with the request URL could trigger it. Here’s what the request URL looked like for the GetProducts operation:

https://exp-backend01.azurewebsites.net:443/api/GetProducts/triggers/GetProducts/invoke

I tested the Logic App using Postman, and it worked as expected. Once I had the backend service working, I moved on to integrating it with API Management.


Setting Up Azure API Management

Next, I configured my API Management instance to route requests to the Logic App. I created a new API within APIM to expose the Logic App endpoint.

Here’s how I configured it:

  • Name: Products-Catalogue
  • Base API URL: https://exp-aim-5000-apim.azure-api.net
  • Operations:
    • GET /products → mapped to the Logic App’s GetProducts operation
    • POST /products → mapped to the Logic App’s PostProducts operation

I tested the GET /products operation directly from the API Management Test Console, and it successfully called the Logic App endpoint.


Adding Inbound Processing Policies

To ensure the API requests included the necessary query parameters (like the SAS token), I added an inbound processing policy in APIM. This policy automatically appends the required parameters to every request, simplifying the call for the client.

Here’s the policy I used:

<policies>
    <inbound>
        <base />
        <set-method>GET</set-method>
        <rewrite-uri template="/api/GetProducts/triggers/products/invoke" />
        <set-query-parameter name="api-version" exists-action="override">
            <value>2022-05-01</value>
        </set-query-parameter>
        <set-query-parameter name="sp" exists-action="override">
            <value>/triggers/products/run</value>
        </set-query-parameter>
        <set-query-parameter name="sv" exists-action="override">
            <value>1.0</value>
        </set-query-parameter>
        <set-query-parameter name="sig" exists-action="override">
            <value>yTsjEIMhgpNQgwRe0Pzmp5T6TJsmX-GwoaORbmKg1M8</value>
        </set-query-parameter>
        <set-header name="Content-Type" exists-action="override">
            <value>application/json</value>
        </set-header>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

This policy made sure that clients didn’t need to worry about adding query parameters or headers. APIM handled everything behind the scenes.


At this point, I had a working integration between my desktop app, API Management, and Logic App. The next step was to test the setup and confirm it was functioning correctly before moving on to securing the backend with managed identities.

In the next section, I’ll walk through how I tested the integration and the results I got.

Testing the Basic Integration

Once I had the basic setup in place, I wanted to test the entire flow to ensure the desktop app could call the API Management endpoint and trigger the Logic App successfully. This involved testing both operations (GetProducts and PostProducts) and confirming that API Management handled the requests correctly.


Testing from API Management Test Console

To begin, I tested the GET /products operation directly from the API Management Test Console in the Azure Portal.

Here’s what I did:

  • Opened my API Management instance in the Azure Portal.
  • Selected the Products-Catalogue API.
  • Chose the Get Products operation.
  • Clicked Test, then hit Send.

Result:
The request was successfully routed to the Logic App, and I received a 200 OK response along with the expected JSON response from the Logic App.

Sample Response:

HTTP/1.1 200 OK  
content-type: application/json; charset=utf-8  
{  
  "products": [  
    { "id": 1, "name": "Laptop", "price": 1200 },  
    { "id": 2, "name": "Headphones", "price": 150 }  
  ]  
}

This confirmed that API Management was correctly rewriting the request URL, appending the query parameters, and calling the Logic App backend.


Testing from Postman

Next, I tested the API from Postman to simulate how the desktop application would make requests.

Here’s how I set it up:

  • Request Type: GET
  • URL: https://exp-aim-5000-apim.azure-api.net/products
  • Headers:
    • Content-Type: application/json

I sent the request and got the same successful 200 OK response.


Testing with the Desktop App

Finally, I integrated the API call into my desktop application. Since it’s a simple .NET app, I used the HttpClient class to send the request.

Here’s the basic code I used:

using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        var client = new HttpClient();
        client.BaseAddress = new Uri("https://exp-aim-5000-apim.azure-api.net");

        var response = await client.GetAsync("/products");

        if (response.IsSuccessStatusCode)
        {
            var content = await response.Content.ReadAsStringAsync();
            Console.WriteLine("Response: " + content);
        }
        else
        {
            Console.WriteLine("Error: " + response.StatusCode);
        }
    }
}

Result:
The app successfully called the API Management endpoint, and the Logic App returned the expected product list.


Summary of Basic Integration Testing

Here’s what I confirmed during my testing:

  • API Management successfully routed requests to the Logic App.
  • The SAS token was correctly added by the inbound policy, simplifying the client request.
  • The desktop app could make API calls without any issues.

At this point, the basic integration was working as expected. However, the setup was still using a static SAS token, which isn’t ideal from a security perspective. Anyone with the API endpoint and token could call my Logic App.

In the next section, I’ll walk through how I secured the backend service using OAuth 2.0 and managed identities to eliminate the need for SAS tokens and improve the overall security of the solution.

OAuth Authentication Enabled Backend

We’ll add some Authentication to the API. At present, anyone can trigger both our Logic App URI and our API Management Operation URI endpoints. We’ll add authentication.

Logic App API Endpoint: https://exp-backend01.azurewebsites.net/api/GetProducts/triggers/products/invoke?api-version=2022-05-01&sp=%2Ftriggers%2Fproducts%2Frun&sv=1.0&sig=yTsjEIMhgpNQgwRe0Pzmp5T6TJsmX-GwoaORbmKg1M8

Azure API, Get Products Endpoint: https://exp-aim-5000-apim.azure-api.net/products

Implementation Steps

  1. Create a new App Registration (this is required for OAuth Authentication). Keep a note of the ClientId (9b534262-f48e-43bb-bdef-6c1f15936a99). We will need this during the creation of the policy in APIM.
  2. Create a Logic App Standard workflow with a HTTP Request Trigger. Enable Managed Identity for the Logic App. This is the same procedure as the basic implementation.
  3. Create an API Management Resource. Enable Managed Identity for the API Management resource.
  4. Add the API Management Identity to the Logic App Resource. I added the Logic App Contributor role. This will allow API Management to communicate with Azure Logic App.

In Azure API Management do the following two item: Create a new API and add Operations to the API.

Create a new API Management API with the following details:

  • Display Name: Products-Catalogue
  • Name: products-catalogue
  • Web Service URL: https://exp-backend01.azurewebsites.net/api/ (note, that this is the first part of the Azure Logic App trigger URI).

This will automatically create a backend service with the name: https://exp-backend01.azurewebsites.net/api/

Next, create a new Operation with the settings described below.

  • Display Name: List Products
  • Name: list-products
  • URL: GET: /GetProducts/triggers/products/invoke?api-version=2022-05-01 (note, that this is the second part of the Azure Logic App trigger URI up to the API Version. Also note that the SAS Token has been removed).

Why is invoke?api-version=2022-05-01 Required?

This part of the URL is essential because:

  • invoke: This keyword tells the Logic App to trigger the specific workflow.
  • api-version=2022-05-01: The Logic App expects an API version parameter to understand the request format. Omitting this will result in a 400 Bad Request or 404 Not Found error.

The api-version parameter specifies the version of the Logic App REST API you’re calling. In my case, 2022-05-01 was the version used, which is the latest supported version for Logic Apps (Standard Workflow) as of now. It’s essential to always include this in your request.

Test the Operation in API Management

Running a test in API Management Test Console with the managed identity enabled will result in the following:

Inbound API Management Policies

For the List Products operation, notice that I have defined a very simple API Management policy with a new reference to authenticate with the managed identity: <authentication-managed-identity resource="9b534262-f48e-43bb-bdef-6c1f15936a99" />

<!--
    - Policies are applied in the order they appear.
    - Position <base/> inside a section to inherit policies from the outer scope.
    - Comments within policies are not preserved.
-->
<!-- Add policies as children to the <inbound>, <outbound>, <backend>, and <on-error> elements -->
<policies>
    <!-- Throttle, authorize, validate, cache, or transform the requests -->
    <inbound>
        <base />
        <authentication-managed-identity resource="9b534262-f48e-43bb-bdef-6c1f15936a99" />
    </inbound>
    <!-- Control if and how the requests are forwarded to services  -->
    <backend>
        <base />
    </backend>
    <!-- Customize the responses -->
    <outbound>
        <base />
    </outbound>
    <!-- Handle exceptions and customize error responses  -->
    <on-error>
        <base />
    </on-error>
</policies>

Once the above has been completed, running a test in APIM should do the following: