Create a secure access to your API on Azure with Front Door and the API Management service

Emanuele Pecorari
10 min readJan 2, 2023

--

This article describes how expose APIs using Azure services like

  • Front Door
  • Api Management
  • Azure Function
  • Azure AD

with a secure approach and avoiding VNET integrations and complex segregated network architecture.

Azure Front Door

As Microsoft describes at https://learn.microsoft.com/en-us/azure/frontdoor/classic-overview

Azure Front Door (classic) is a global, scalable entry-point that uses the Microsoft global edge network to create fast, secure, and widely scalable web applications.

Front Door guarantees to forward the requests to the most available and fastest backend of your architecture and offers wide range of traffic routing options and backend health monitoring system. It’s failure tolerant even when an entire region is impacted.

The service has 2 different plans:

  • classic
  • standard/premium

A comparison overview can be found at https://learn.microsoft.com/en-us/azure/frontdoor/standard-premium/tier-comparison

This article is based on the classic version for which the price is based on:

  1. Data transfer out (i.e., data going out of edge location to the client)
  2. Data transfer in (i.e., data coming into edge location from the client)
  3. Routing Rules (i.e., the number of routes configured for your Front Door). Each route is a distinct combination of http/https protocol, front-end hosts/domains, and path patterns.
  4. Bandwidth (i,e., data transfer between AFD and Azure website)

It’s possible to attach WAF capabilities to Front Door to make secure the access to your web services.

API Management

It’s the Azure service to expose and secure APIs. It’s available in 4 different plans:

  • Consumption
  • Basic
  • Developer
  • Standard
  • Premium

The article uses the Consumption version based on a pay-as-you-use approach.

Create the function

In the Azure Portal, search for “Function App” and then click the ‘Create function app” button.

Enter the data for your application like in the example below (be sure to choose Consumption plan):

Creation of the function app

Once the Function app is created (you can find searching for the ‘Function app’ section in the search bar), click on it, go to ‘Functions’ and click on the button ‘Create’:

Creation of the app

For simplicity choose the option “Develop in portal” for development environment.

Click on “HTTP Trigger” in the “Select Template” and let’s keep the rest with default values (the function name will be “HttpTrigger1”).

The function can be called using the url (the url is visible going to the “Code+Test” tab and clicking on “Get function URL”:

https://<functionapp_name>.azurewebsites.net/api/<function_name>?code=<function_key>

Create the API Management

Go to the overview of the Function App and on the left, click “API Management” and choose “Create new” when the interface asks to choose an instance to attach the function to.

Fill in the required values in the first form, click next until you reach the Managed identity section. Check the box in “System assigned managed identity”;

System managed identity for the API Management

Wait until the API management instance is created and go back to the section “API management” of the function app: now you should see the message confirming they are linked.

Function and API Management link confirmation message

Click “Go to API Management” and on the left menu click on “API” and then “+ Add API” and choose “Function App” in “Create from Azure Resource”

Create API from Azure Resource

Click “Select” to open the slider where the previously created function can be selected. Click “Save”

Select the function to create the API

The two methods GET and POST for our function are now exposed.

Created API resources

Clicking on “Test” link at the top and then on the button “Send” in the next view, we can check that the call on the API reaches correctly the function

Test API Management

Secure call between API Management and the Function

We want to be sure that our function is callable only by the API management and the rest of the call (even with a valid code) get a 401. We’ll rely on the Active Directory service of Azure to do that.

Go to the Function app and click “Authentication” on the left and when the panel opens, click “Add identity provider”.

In the next form choose the following:

  • Identity provider: Microsoft
  • “Create new app registration”
  • “Current tenant”
  • Choose 401 as error for unauthenticated errors

keep the other default values and click “Next” and “Add” in the permissions tab. Take note of the client id shown in the screen confirming the enablement of the authentication.

Now if you go back to the test section of your API Management you will see that you get a 401 Unauthenticated error.

Configure the API Management to get the token to call the function

The previously created API Management instance has its own system assigned instance from Active Directory and it is in the same tenant of the function so it would be able to call the API if it authenticates itself.

To do that, we can change the policy of API Management. Go to the instance and select “test-frontdoor” and click “All Operations”.

The created API resources

Click on the </> icon in “Inbound processing” box (that gives us access to the policy editor) and paste the following policy :

<!--
IMPORTANT:
- Policy elements can appear only within the <inbound>, <outbound>, <backend> section elements.
- Only the <forward-request> policy element can appear within the <backend> section element.
- To apply a policy to the incoming request (before it is forwarded to the backend service), place a corresponding policy element within the <inbound> section element.
- To apply a policy to the outgoing response (before it is sent back to the caller), place a corresponding policy element within the <outbound> section element.
- To add a policy position the cursor at the desired insertion point and click on the round button associated with the policy.
- To remove a policy, delete the corresponding policy statement from the policy document.
- Policies are applied in the order of their appearance, from the top down.
-->
<policies>
<inbound>
<base />
<authentication-managed-identity resource="<registration_app_client_id>" />
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>

where registration_app_client_id is the value of the above noted client id.

Now, going back to the test screen of the api management for the GET /HttpTrigger1 resource will work again.

On the other hand, if you try to access your function from a browser with the URL with the secret code, you will get a 401 error.

Secure call to API Management

If you want to test the call to the API management outside the portal, you will need to add the subscription key in the request’s header.

To create one, to go the API Management instance, on the left menu click “Subscriptions” then, click on the button “Add subscription” and add some data similar to those below and then “Create”:

Add subscription key

Once back in the keys list, on the just created key row, go to the right and click the three dots and in the open menu choose “Show/hide keys”: you will be able to copy the key value.

Now you can execute the request using the copied value:

curl --location --request GET 'https://<apim_url>/test-frontdor/HttpTrigger1' \
--header 'Ocp-Apim-Subscription-Key: <key>'

We might enhance the API management protection configuring the OAuth access using again Azure AD. This will have the benefits to ask to the users who need to access the API (that might be implemented with several Function Apps as backends) to authenticate themselves. Let’s keep this out of the scope for this article and let’s stick to the subscription key (that can be regularly changed after a specific amount of time).

Force the connection to API through Front Door and WAF

Right the API management exposes a public URL (the one used in the previous test). Any system can try to use it as long as it owns the key.

Let’s do a step further trying to protect the APIs against the top 10 OWASP threats. To do that we need a global service that might integrate the WAF capabilities. Also, we need to be sure that all the flows goes through this service without the possibility to access directly the API management URL.

Front Door is the service that we can use in this case. Let’s create our instance. In the Azure search bar enter “Front Door” and click on the result.

Click the button ‘Create’, then on “Continue to create a Front Door”, choose the resource group , enter the name, select “Explore other offerings” and be sure to click on “Azure Front Door (classic)”.

As endpoint name we chose ‘test-frontdoor’: the interface showed the hostname preview at which our services will be reachable.

Choose “API Management” as “Origin type” which will make selectable the URL of our API Management in “Origin host name”.

In the “WAF Policy” section click “Create new” and enter a name for it. Finally click “Review + Create” and the “Create” button.

After a while our Front Door is ready. Click on it and in the “Settings” section choose ‘Front Door manager”. Click on he “default-route”, in the “Forward policy” let’s choose “only HTTPS” and save.

Once back, above the “Route” box we can see the endpoint. Let’s copy it (in our case test-frontdoor-e2gnarazecg7fvgq.z01.azurefd.net).

Now, we can call our API also through this endpoint:

curl --location --request GET 'https://<frontdoor_endpoint>/test-frontdoor/HttpTrigger1' \
--header 'Ocp-Apim-Subscription-Key: <key>'

Now, both the endpoints are reachable and usable: we don’t want that. We want that all the traffic, now, flows through the Front Door instance and that the API management accepts only traffic coming from that.

The page https://learn.microsoft.com/en-us/azure/api-management/front-door-api-management describes how to do that:

  1. Restrict incoming IP addresses to your API Management instances
  2. Restrict traffic based on the value of the X-Azure-FDID header

Restring incoming IP addresses

Let’s use the Azure API to retrieve the IP ranges that Front Door uses. Visit the page https://learn.microsoft.com/en-us/rest/api/virtualnetwork/service-tags/list?tabs=HTTP and click on the red button “Try it” above the endpoint. Choose your Azure account to sign in and continue. Enter a region in location field (“West Europe”) and choose the subscription.

Now click the “Run” button and get the result. Take the big JSON returned as result and save it in a file (azure_ip.json)

A Python script can help us to quickly create the ‘ip-filter’ policy to apply on the API management instance.

import json
import sys
import ipaddress

input_file_path = sys.argv[1]
output_file_path = sys.argv[2]

f = open(input_file_path)

json_data = json.load(f)


output_dict = [x for x in json_data["values"] if x["name"] == "AzureFrontDoor.Backend"]

with open(output_file_path, 'w') as writer:
writer.write('<ip-filter action="allow">')

for ip_ranges in output_dict:
ip_list = ip_ranges["properties"]["addressPrefixes"]
for cidr in ip_list:
lowest_and_max_ip = ipaddress.ip_network(cidr)
lowest_ip = lowest_and_max_ip[0]
max_ip = lowest_and_max_ip[-1]
range_for_policy = f'\n\t<address-range from="{lowest_ip}" to="{max_ip}" />'
writer.write(range_for_policy)

writer.write("\n</ip-filter>")


f.close()

The script accepts as input parameters the path to the just downloaded azure_ip.json file and the path for the xml file that will contain the policy.

Suppose the script is called ip_extractor.py, the command to run can be:

python ip_extractor <input_file_path> <output_file_path>

In the output file we’ll find something like this:

<ip-filter action="allow">
<address-range from="4.232.98.120" to="4.232.98.127" />
<address-range from="13.73.248.16" to="13.73.248.23" />
....
</ip-filter>

Copy the content of the XML file, go to the create API Management instance, select “test-frontdoor” and click “All Operations”. Click on the </> icon in “Inbound processing” box (that gives us access to the policy editor) and paste it under the previous “authentication-managed-identity” block previously added and click “Save”.

Now, executing the request directly to the API Management URL will produce a 403 http error with the following response body:

{
"statusCode": 403,
"message": "Forbidden"
}

while the request via the Front Door URL works as expected.

There still one step to do to completely make secure our architecture. At this moment, our API Management service might receive requests from any Front Door instance created outside our subscription since the IP ranges that we have used in our policy are used by any Front Door instance that is created globally in Azure. In the next paragraph we’ll make sure that only the traffic coming from our Front Door is allowed.

Restrict Front Door traffic using the X-Azure-FDID header

First let’s retrieve the id of our Front Door.

Go to Front Door, click on the created instance. The overview panel contains an attribute called Front Door ID. Copy it.

This value is added automatically in the X-Azure-FDID header at every request flowing through Front Door. If the header is already present in a request, Front Door will overwrite the value with its own configuration.

Now, add the following block under the “ip-filter” block in the API management policy already modified above:

<check-header name="X-Azure-FDID" failed-check-httpcode="403" failed-check-error-message="Invalid request." ignore-case="false">
<value><your_frontdoor_id></value>
</check-header>

Everything will be in the inbound section. Click ‘Save’. Now only the requests coming from the Front Door instance in our subscription can reach our API Management.

Our architecture is now complete and secure.

--

--

Emanuele Pecorari
Emanuele Pecorari

Written by Emanuele Pecorari

Cloud Architect and Tech Product Owner. Soccer player and coach in the free time.

No responses yet