Connect backend to Azure API Management

Last Update: 12/24/2019
Azure
API Management

Learn how to use the ARM Template to add APIs to API Management. Use resources such as operations, policy and backend to connect APIs to your backend. In this article we assume that the backend is implemented in Azure functions.

Define the API

First, set the API definition in API Management. In API Management, each API is defined by a resource called operations.

Define operations

operations is a resource corresponding to one API. You can define the API path and parameters here in this resource. Let's take a look at the ARM Template for operations.

operations
{
  "name": "[concat(parameters('serviceName'), '/', parameters('apiName'), ';rev=', parameters('apiRevision'), '/', parameters('operationName'))]",
  "type": "Microsoft.ApiManagement/service/apis/operations",
  "apiVersion": "2018-01-01",
  "dependsOn": [
    "[resourceId('Microsoft.ApiManagement/service/apis', parameters('serviceName'), concat(parameters('apiName'), ';rev=', parameters('apiRevision')))]",
    "[resourceId('Microsoft.ApiManagement/service/backends', parameters('serviceName'), parameters('backendName'))]"
  ],
  "properties": {
    "description": "GET Order",
    "templateParameters": [
      {
        "name": "orderId",
        "type": "string",
        "values": []
      }
    ],
    "responses": [],
    "policies": null,
    "displayName": "GetOrder",
    "method": "GET",
    "urlTemplate": "orders/{orderId}"
  },
  "resources": []
}

Note that the name is appended with rev;={apiRevision}, since it will be deployed as a specific revision as mentioned in other article 3. Url is specified in urlTemplate. Variable parameters are expressed as {orderId} format. The details of the parameters are described in templateParameters. The above example defines that the type of orderId is string. For method, specify the HTTP Method.

The API created from the above example will be GET http(s)://{API Management FQDN}/api/v1/orders/{orderId}. What we defined this time is the /orders/{orderId} part, and the /api/v1 part was defined in the previous apis and apiVersionSets 3. You can import OpenAPI.yaml to define API. I'll cover that in a separate article.

Define backends

Next, define the backends.

backends
{
  "name": "[concat(variables('serviceName'), '/', parameters('backendName'))]",
  "type": "Microsoft.ApiManagement/service/backends",
  "apiVersion": "2018-01-01",
  "dependsOn": [
    "[resourceId('Microsoft.ApiManagement/service/properties', parameters('serviceName'), parameters('propertyName'))]"
  ],
  "properties": {
    "title": null,
    "description": "Backend of API management",
    "url": "[concat('https://', parameters('functionsAppName'),'.azurewebsites.net/api')]",
    "protocol": "http",
    "credentials": {
      "query": {
        "code": ["[concat('{{', parameters('propertyName'), '}}')]"]
      }
    },
    "resourceId": "[concat('https://management.azure.com/subscriptions/', subscription().id, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', parameters('functionsAppName'))]"
  }
}

backends are literally resources that are equivalent to the backend services that make up the API. This time, we assume HTTPTrigger function of Azure functions as backend. The url of the Azure functions is specified in url, and the resource of the Azure functions is specified by resourceId. In this case, the route of HTTPTrigger of Azure functions must be orders/{orderId}.

In credentials, specify the API Key to connect with the Azure functions. The template example above means that credentials are specified in the form of a query parameter such as ?code=xxx, and the character string specified by propertyName is set. The value of credentials is in the form of double enclosing, such as {{credential}}. Credentials such as API Key are stored in the properties resource described below. The propertyName specified here is the name that indicates the credentials saved in properties.

By the way, in order to specify the API Key of the Azure functions here, you need to get the Key from the Azure functions. It is common to use the Template functions listSecrets or listKeys to get keys in ARM Template, but it does not work as expected for Azure functions v2 1. It is better to get it in advance using Azure CLI 2.

azure-cli
$resourceId = "/subscriptions/$subscriptionid/resourceGroups/$resourceGroup/providers/Microsoft.Web/sites/$functionsAppName"
$url = "$resourceId/host/default/listKeys?api-version=2016-03-01"
$ret = az rest --method post --uri $url | ConvertFrom-Json
$key = $ret.functionKeys.default

Azure functions have a host key and a function key. In this case, the host key is used. Simply put, the function key is a key for each function, and the host key is a key that can be connected to all functions.

In order to connect APIs with the backend, you need to specify the backend in the policy as well.

Define policy

Finally, set a policy to connect backend and API Management. Policy is an important feature that can add various processing to API Management. Here we use one of them to connect to the backend.

policy
{
  "variables": {
    "backendPolicy": "[concat('<policies>\r\n  <inbound>\r\n    <base />\r\n    <set-backend-service id=\"apim-generated-', 'policy','\" backend-id=\"', parameters('backendName'), '\" />\r\n  </inbound>\r\n  <backend>\r\n    <base />\r\n  </backend>\r\n  <outbound>\r\n    <base />\r\n  </outbound>\r\n  <on-error>\r\n    <base />\r\n  </on-error>\r\n</policies>')]"
  },
  "resources": [
    {
      "name": "[concat(parameters('serviceName'), '/', parameters('apiName'), ';rev=', parameters('apiRevision'), '/', 'policy')]",
      "type": "Microsoft.ApiManagement/service/apis/policies",
      "apiVersion": "2018-01-01",
      "properties": {
        "policyContent": "[variables('backendPolicy')]",
        "contentFormat": "xml"
      }
    }
  ]
}

policy is specified in xml in policyContent. backendPolicy is defined invariables, and you can see that the xml string is defined. The point is that the backend resource is specified by backend-id. As a result, the backend resource created earlier is specified in the policy.

Define properties

properties is a resource to save the setting value used in API Management in key-value format. Here, we will use this resource to hold the API Key to connect to the backend azure functions.

properties
{
  "type": "Microsoft.ApiManagement/service/properties",
  "name": "[concat(parameters('serviceName'), '/', parameters('propertyName'))]",
  "apiVersion": "2018-06-01-preview",
  "scale": null,
  "properties": {
    "displayName": "[parameters('propertyName')]",
    "value": "[parameters('functionHostKey')]",
    "tags": ["key", "function"],
    "secret": true
  }
}

Replace displayName with the name of the property you specified earlier in backends. Specify a secret value for value. In this case, specify the API key of the Azure functions obtained earlier with the Azure CLI. tags is a tag to use when searching for properties later. Let's specify an arbitrary character string. By setting secret to true, the property will be encrypted and kept secure.

Deploy

So, let's deploy the ARM Template based on what we have explained so far.

operations.json
{
  "$schema": "https://schema.management.azure.com/schemas/2018-05-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "serviceName": {
      "type": "string",
      "metadata": {
        "description": "Service Name"
      }
    },
    "apiName": {
      "type": "string",
      "metadata": {
        "description": "API Name"
      }
    },
    "apiRevision": {
      "type": "string",
      "metadata": {
        "description": "API Revision"
      }
    },
    "operationName": {
      "type": "string",
      "metadata": {
        "description": "Operation name"
      }
    },
    "backendName": {
      "type": "string",
      "metadata": {
        "description": "Backend Name"
      }
    },
    "functionHostKey": {
      "type": "securestring",
      "metadata": {
        "description": "Host key for the functions app"
      }
    },
    "functionAppName": {
      "type": "string",
      "metadata": {
        "description": "Functions App Name"
      }
    },
    "propertyName": {
      "type": "string",
      "metadata": {
        "description": "Property Name"
      }
    }
  },
  "variables": {
    "backendPolicy": "[concat('<policies>\r\n  <inbound>\r\n    <base />\r\n    <set-backend-service id=\"apim-generated-', 'policy','\" backend-id=\"', parameters('backendName'), '\" />\r\n  </inbound>\r\n  <backend>\r\n    <base />\r\n  </backend>\r\n  <outbound>\r\n    <base />\r\n  </outbound>\r\n  <on-error>\r\n    <base />\r\n  </on-error>\r\n</policies>')]"
  },
  "resources": [
    {
      "name": "[concat(parameters('serviceName'), '/', parameters('apiName'), ';rev=', parameters('apiRevision'), '/', parameters('operationName'))]",
      "type": "Microsoft.ApiManagement/service/apis/operations",
      "apiVersion": "2019-01-01",
      "dependsOn": [
        "[resourceId('Microsoft.ApiManagement/service/backends', parameters('serviceName'), parameters('backendName'))]"
      ],
      "properties": {
        "description": "GET Order",
        "templateParameters": [
          {
            "name": "orderId",
            "type": "string",
            "values": []
          }
        ],
        "responses": [],
        "policies": null,
        "displayName": "GetOrder",
        "method": "GET",
        "urlTemplate": "orders/{orderId}"
      },
      "resources": []
    },
    {
      "name": "[concat(parameters('serviceName'), '/', parameters('apiName'), ';rev=', parameters('apiRevision'), '/', 'policy')]",
      "type": "Microsoft.ApiManagement/service/apis/policies",
      "apiVersion": "2018-01-01",
      "properties": {
        "policyContent": "[variables('backendPolicy')]",
        "contentFormat": "xml"
      }
    },
    {
      "name": "[concat(parameters('serviceName'), '/', parameters('backendName'))]",
      "type": "Microsoft.ApiManagement/service/backends",
      "apiVersion": "2018-01-01",
      "dependsOn": [
        "[resourceId('Microsoft.ApiManagement/service/properties', parameters('serviceName'), parameters('propertyName'))]"
      ],
      "properties": {
        "title": null,
        "description": "Backend of API management",
        "url": "[concat('https://', parameters('functionAppName'),'.azurewebsites.net/api')]",
        "protocol": "http",
        "credentials": {
          "query": {
            "code": ["[concat('{{', parameters('propertyName'), '}}')]"]
          }
        },
        "resourceId": "[concat('https://management.azure.com/subscriptions/', subscription().id, '/resourceGroups/', resourceGroup().name, '/providers/Microsoft.Web/sites/', parameters('functionAppName'))]"
      }
    },
    {
      "type": "Microsoft.ApiManagement/service/properties",
      "name": "[concat(parameters('serviceName'), '/', parameters('propertyName'))]",
      "apiVersion": "2018-06-01-preview",
      "scale": null,
      "properties": {
        "displayName": "[parameters('propertyName')]",
        "value": "[parameters('functionHostKey')]",
        "tags": ["key", "function"],
        "secret": true
      }
    }
  ]
}

Now API can be deployed to the service instance using the above template.

2019 Copyright Channel.241