API Management and Azure Functions secured without secret keys leveraging managed identities (RSS of TV series' new episodes)
Published: 2021-11-20 11:57:20

I wanted to keep up with new episodes of my favourite TV shows as they aired; being a big **RSS feed** fan, I wanted to centralise all the news in one place and I built a simple **API** that checks for new episodes and turns them into **RSS XML** for my feed reader and hosted it on cheap **Azure Functions** on **consumption plan**.

![Feed reader](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uerkhgu18j1pz58dfasd.png)

Link to the GitHub repository: [TvShowRss](https://github.com/UnoSD/TvShowRss)

This is the architecture:

![Architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3ge1r9rieou3uxaawid8.png)

### Securing with function keys

Making the **Azure Function** public, I would have opened my solution to abuse attacks.

**E.G.**

Someone knowing the **URL**, could run a massive invocation of my functions; given that **Azure Functions** scales seamlessly with no virtual limit, I could end up with a colossal bill on my **Azure subscription**.

So I decided to add an **API Management** instance in consumption plan in front of the functions **API** and set quotas, so only a certain amount of executions would be allowed per minute.

![Rate limit](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dmq35h1u2ekxnzjppnci.png)

I wanted also to restrict access to my functions only from the **outbound IP ranges** of my **APIM** instance, but the consumption plan does not have a specific outbound range, so I had to secure it only with function authorisation (for now).

To secure the **back-end** I initially restricted the functions with **function keys**; so only people in possession of that key could invoke my functions.

This well known pattern, carries a big burden: to make sure your solution is secure, you need to have a key rotation system in place, in case someone manages to steal that secret value.

### Authentication policies

Sometimes in 2021, a new feature was introduced in **API Management**, the authentication policies: https://docs.microsoft.com/en-us/azure/api-management/api-management-authentication-policies

With this feature, I could leverage the **managed identity** of the **APIM** instance and get rid of the keys completely.

### Easy Auth

There is another amazing feature of **App Services/Functions** required, this is **Easy Auth**; this name is not well known, and the feature in the portal is just called **Authentication/Authorization**, which is a quite generic term and makes it harder to find in search engines. This is the link: https://docs.microsoft.com/en-us/azure/app-service/configure-authentication-provider-aad

This feature takes the burden of auth away from your application code and gets implemented at **PaaS** level.

You just configure your app to block non-authenticated traffic and it does the rest for your (create **AAD App Registration**, configure it, block anonymous access, check authentication token on calls):

![Easy Auth](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gugwsvr6qp0chul1qu3y.png)

![Easy Auth set up](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0anirh4pxcdv6m1yebdk.png)

Now you can set up your **Functions** as back-end of **API Management** and add the `authentication-managed-identity resource` policy inbound, which will automatically add the `Authorization` header to the requests and authenticate seamlessly, without stored secrets to your functions.

![Policy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6k6lu2jugtlbwgbyruvm.png)

### Unexpected problem

I was confident that it was going to take a few minutes to enable this and remove all the references to the old function key, but when I ran my test I had the following error:

```
IDX10214: Audience validation failed. Audiences: ‘[PII is hidden]’. Did not match: validationParameters.ValidAudience: ‘[PII is hidden]’ or validationParameters.ValidAudiences: ‘[PII is hidden]’.
```

Helpful, isn't it?

I kept looking at my audience in the **JWT** token from the **APIM** trace and at the allowed audiences in **Easy Auth** and they were exactly the same!

**APIM trace**

![APIM trace](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yq3gflwwwu1mbqj4g6xe.png)

**JWT.MS inspection**

![Token](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/97rz8f7b8x3xfy9ozdyz.png)

**Easy Auth config**

![Easy Auth](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s9wykebbkp6f5bpgue61.png)

OK, the data is obfuscated, but you can trust me on that one, the rest of the `27c65941-` GUID, matched everywhere.

The PII hidden did not help as I was not able to see the difference in the error message. If you try and find how to allow PII to be shown in debug environments, that can be done by setting a property in code, but the **Easy Auth** code is not something we can change.

Then I stumbled across [this](http://www.mattruma.com/adventures-with-azure-doh-built-in-app-service-identity-provider-not-working/) article from a fellow CSA and I figured it out!

Despite the error message talking about audience, the problem was with the issuer! If you look at the **Easy Auth** configuration, it is requesting `v2.0`, whereas the auto-generated **AAD application** issues `v1.0` by default.

So I changed that in the app manifest et voilà... authentication worked and I was able to get rid of the secrets.

### Infrastructure as code

OK, that's sorted, but it was time to script the changes and make sure my **Pulumi** deployment created the **app registration** with the correct configuration.

I started typing in **Visual Studio** in **C#**, under the **Application** definition: `AccessTokenAccep<ctrl+space><ctrl+space><ctrl+space><ctrl+space>` but no joy from IntelliSense...

This property wasn't in the **Pulumi** class! Which is odd, considering **Pulumi** auto generates code from the **REST API** (I presume also for the **Azure AD** module now).

So I looked up in the **Azure AD application REST API** docs and... nothing. Turns out (see [here](https://stackoverflow.com/questions/57826919/azure-ad-how-to-set-app-manifest-properties-programatically-accesstokenaccept)) it was only available on the beta API.

So I had to include the **REST API** call in **Pulumi** after the creation of the **app registration**, using a workaround to force the call that is my code but I will not discuss in this article (see code [here](https://github.com/UnoSD/TvShowRss/blob/a1bad97ba1d5defa205cfeb985472a32d8a18c34/Infrastructure/Program.cs#L221)).