API Management - OAuth and private back-ends

Azure API Management is a fully managed API Gateway service.

In my last article we looked at the anatomy of the service. Today I wanted to demonstrate how to use OAuth with JWT token to protect an API Front End. I also wanted to show how we can access backend APIs on private networks.

There are a few other interesting bits:

Probably the biggest contribution of this article is to show how to do this all with an ARM template. ARM template doesn’t only allow to quickly build a demo environment, but it enables Dev-Ops. API Management has a notoriously complicated ARM model, but with time and patience and the help of this DevOps resource kit, you too can make it.

Why would we want to use OAuth / JWT to protect our API? By default, an API in API Management is protected using a subscription key. This is good but it can be leaked. A JWT token is short lived and hence is a little stronger. If we use Managed Service Identity (MSI) in consumer, it is trivial to acquire a JWT and impossible to leak the secret (certificate) of the MSI. So the solution becomes much more secure.

As usual, the code is in GitHub.

Sample solution

Here is the solution we are going to build:

Sample Solution

We have an API Management service in the middle. It is integrated to a VNET in external mode. Since it is part of a VNET, it can communicate with other VNET-bound service. The diagram shows a private IP ; it isn’t a service private VIP as it is deployed in external mode. Rather this represents the private IP of whatever VM happened to communicate with a private service.

The private service is implemented using an Azure Container Instance for pure convenience: it is quick and simple to deploy.

The public service is implemented with a Logic App, because it’s simple to implement and monitor.

The public consumer also is implemented with a Logic App for similar reasons.

We can easily deploy the solution:

Deploy button

The ARM template has 4 parameters:

Parameter Mandatory Description
organizationName X Name of the organisation for API Management. This is also used for the name of the service.
adminEmail X Email for the admin
tenantName X Name of the Azure AD tenant (related to following parameter)
appId X Azure AD Application ID ; this application is used to authenticate API users

As usual, to author the ARM template, we looked at the online documentation, reverse engineered a deployment made from the portal, checked out some Azure Quickstart Templates and use our imagination.


Once we’ve deployed the ARM Template, we can take a quick look at the resources in the Portal:

Virtual Network

We can see we have 2 subnets. One for the API Management VMs and one for the private service. The latter is delegated to the Azure Container Instance (ACI)service. This is required to deploy an ACI inside a subnet.


Azure Container Instance

Nothing much to say about the private service. We can see the container image is from Docker Hub:

Container image

We are basically using the same image we’ve built in the Docker Getting Started which is basically a Python Flask Hello World.

This is the service we are going to screen scrap.


This service calls an API Management front end using a subscription key.


This service calls an API Management front end using a subscription key and a JWT token.

This is done using a Managed Service Identity (MSI) in Logic App.


This is the public service. It’s a simple HTTP-post service expecting a JSON body.

If we try to test the app, it would fail. The reason is that we locked down the app to accept request only from our instance of API Management:

Access Control

(A /32 means a single IP)

We can do that because the outbound public IP of an API Management Service is static (it doesn’t change). This is explained in the FAQ.


Let’s look at the APIs in API Management:


The Echo API comes as a sample with each API Management service and can be safely deleted.

The one-api is the one we deployed. It has three operations.


The public operation maps to the public-service Logic App.

It is configured as an HTTP-get taking two query string parameters. The transformation occurs in the inbound policies:

    <base />
    <set-backend-service base-url=" {{public-service-url}} " />
        var body=new

        return JObject.FromObject(body).ToString();
    <rewrite-uri template=" {{public-service-query-string}} " copy-unmatched-params="false" />
    <set-header name="Content-Type" exists-action="override">

A couple of things to say about those policies:

With only 19 lines of code we were able to transform the API front the frontend to the backend.

We can test the operation and get the simple result.


This operation simply returns the response from the container. Nothing fancy here:

    <base />
    <set-backend-service base-url=" {{private-service-url}} " />
    <rewrite-uri template="/" copy-unmatched-params="false" />

If we test the operation and get a result similar to:

<h3>Hello World!</h3><b>Hostname:</b> wk-caas-5fcd43c10fa6404e87ca36b291c59013-4dda47657e18158b8ed3d7<br/><b>Visits:</b> undefined

The string starting with wk-caas-5f… is the container ID. This one will vary each time the ACI is deployed.


Here we do some screen scraping. We used the same back-end service, i.e. the Azure Container Instance, but we post-process its response:

    <base />
    var raw=context.Response.Body.As<string>(true);
    var startIndex = raw.IndexOf("</b>") + 4;
    var endIndex = raw.IndexOf("<br/>");
    var host = raw.Substring(startIndex, endIndex - startIndex).Trim();
    var response = new { hostName = host };

    return JObject.FromObject(response).ToString();

The C# code might look a little cryptic but basically, we do a couple of string manipulation to extract the container ID and return it in a JSON response. If we test it:

  "hostName": "wk-caas-5fcd43c10fa6404e87ca36b291c59013-4dda47657e18158b8ed3d7"

Here we can see how we can easily transform APIs again.


Now if we look at the products, we see the standard Starter & Unlimited, but we also see two custom ones:


Subscription based product is pretty vanilla. The Token based one is interesting. If we look at its policies:

    <base />
    <validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid." output-token-variable-name="jwt">
        <openid-config url="https://login.microsoftonline.com/ {{tenant-name}} .onmicrosoft.com/.well-known/openid-configuration" />
            <audience> {{app-id}} </audience>
    <set-header name="Authorization" exists-action="delete" />

So here we do the JWT token validation at the product level. This means that every operation under every API belonging to that product will have this validation.

Shout out to Sacha Bruttin for his nice article which explains why we need the last line! The incoming requests will have an Authorization header which gets validated for JWT token. But if we don’t delete that header, it will be passed to Logic Apps (the backend API) which will fail with it. Hence the set-header policy deleting that header at the end.

Now the one-api API belongs to the two products: one requiring a JWT, one not requiring it. Products & API have a many-to-many relationship and here we exploit that for demo purposes.

If we go back to test the public operation but we explicitly choose the product as Token based:

Testing API without token

we will hit a failure:

HTTP/1.1 401 Unauthorized

content-length: 85
content-type: application/json
date: Fri, 12 Jul 2019 22:08:44 GMT
ocp-apim-trace-location: https://apimgmtstcnmes4lawzbtjoj.blob.core.windows.net/apiinspectorcontainer/pt8ZzbIWYyFjQG3dBlRRgg2-2?sv=2018-03-28&sr=b&sig=%2FnJUrpsZ85J3a9RvZmhAGAQ0mCQ43ZfV6ek46xGjCIU%3D&se=2019-07-13T22%3A08%3A45Z&sp=r&traceId=af662f0a7cd44949b15e61fe4f828000
vary: Origin
    "statusCode": 401,
    "message": "Unauthorized. Access token is missing or invalid."

That’s because the testing UI doesn’t send JWT token along with the request.

We can test that API with JWT token by using the public-consumer-token Logic App which does send a JWT token by using its Managed Service Identity (MSI).


We did cover a lot of ground here.

The main points to get across were:

Leave a comment