Azure API Management authentication - Part.1
Published: 2022-04-18 12:39:14

# Summary
In this article, I am going to share how Azure API Management authentication works. [The sample code](https://github.com/koheikawata/api-management-sample) includes three types of authentication APIs - Azure AD, Basic Auth, Client Certificate and two patterns of API Management Gateway validation. In Part.1, the Subscription Key Validation pattern is introduced.

- [Azure API Management authentication - Part.2](https://dev.to/koheikawata/azure-api-management-authentication-part2-pd9)
- [Azure API Management authentication - Part.3](https://dev.to/koheikawata/azure-api-management-authentication-part3-nbp)

# TOC
- [Architecture](#architecture)
- [Subscription key validation](#subscription-key)
  - [Configuraiton - Subscription key](#config-subscription)
  - [Configuraiton - Azure AD token for backend](#config-azuread)
  - [Configuraiton - Basic auth username and password for backend](#config-basicauth)
  - [Configuration - Certificate for backend](#config-certificate)
- [Sample request](#sample-request)

# Architecture <a name="architecture"></a>

- Azure API Management has as backend servers three APIs AzureAdAuthAPI, BasicAuthAPI, CertificateAuthAPI, which are running on Azure App Service
- A request is going to the route depending on `/AuthAdAuth`, `/BasicAuth`, `/CertificateAuth`. For example, the request goes to AzureAdAuthAPI if the request URL is `https://apim-sample.azure-api.net/AzureAdAuth`.
- Authentication (or validation) happens twice for each request. The first one is at API Management gateway, the second is at App Service.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dx9cwba387cfqe2ebh16.png)

- The request includes credentials such as a token, password, subscription key, or certificate.
- *Pass through* is a pattern that those credentials are passed through from the client request to the backend.

**Pass through**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3u18a5nh1fqhki96mbly.png) 

- *Get or generate* is another pattern that those credentials are generated at API Management or acquired from an authorization server or secret storage.

**Get or generate**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kefataugq5kljf7wmbbr.png)

# Subscription key validation <a name="subscription-key"></a>

This is the architecture that API Management validates a request from a client application with Subscription Key, and then gets a token, password, or certificate from Azure AD and Azure Key Vault, and send a backend request. Each Web API on Azure App Service validates the request from the API Management.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2dae3owbbcmy4z94w0wf.png)
 
## Configuraiton - Subscription key <a name="config-subscription"></a>

Requiring the subscription key is configured for each API level. In bicep, you need to turn `subscriptionRequired: true` for all APIs.

[Bicep example](https://github.com/koheikawata/api-management-sample/blob/main/src/bicep/azuredeploy_subscription.bicep)
```
resource ApiManagementApiAzureAd 'Microsoft.ApiManagement/service/apis@2021-08-01' = {
  name: '${ApiManagement.name}/${apim_api_name_ad}'
  properties: {
    displayName: apim_api_name_ad
    subscriptionRequired: true
    serviceUrl: apim_service_url_ad
    protocols: [
      'https'
    ]
    path: apim_api_path_ad
  }
}
```

## Configuraiton - Azure AD token for backend <a name="config-azuread"></a>

- When the API Management receives a request and validates the subscription key, it gets a token from Azure AD that is required to access AzureAdAuthAPI. And then it attaches the token to the request header for the backend.
- Acquiring a Azure AD token is configured in API Management policy of AzureAdAuthAPI.
- OAuth2.0 type is Client Credential because it is a service authentication.
- Delete the subscription key before the request is passed through the backend.

**Policy example**

| Named values        | Comments                               |
|---------------------|----------------------------------------|
| authorizationServer | OAuth2.0 authorization server endpoint |
| clientAppId         | Azure AD application ID of client app  |
| scope               | OAuth2.0 scope                         |
| clientSecret        | Secret of Azure AD client application  |

```
<policies>
    <inbound>
        <base />
        <send-request ignore-error="true" timeout="20" response-variable-name="bearerToken" mode="new">
            <set-url>{{authorizationServer}}</set-url>
            <set-method>POST</set-method>
            <set-header name="Content-Type" exists-action="override">
                <value>application/x-www-form-urlencoded</value>
            </set-header>
            <set-body>@{
              return "client_id={{clientAppId}}&scope={{scope}}&client_secret={{clientSecret}}&grant_type=client_credentials";
          }</set-body>
        </send-request>
        <set-header name="Authorization" exists-action="override">
            <value>@("Bearer " + (String)((IResponse)context.Variables["bearerToken"]).Body.As<JObject>()["access_token"])</value>
        </set-header>
        <set-header name="Ocp-Apim-Subscription-Key" exists-action="delete" />
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>
```

- To set up API Management policy dynamically through Bicep deployment, it needs to register Named Values. Policy refers its dynamic value to Named Valued.
- Secrets should be stored in Azure Key Vault, and Named Value refers to it.
- The Policy has to clarify dependency of Named Values with `dependsOn` in the bicep file.

[Bicep example](https://github.com/koheikawata/api-management-sample/blob/main/src/bicep/azuredeploy_subscription.bicep)
```
resource ApiManagementPolicyAzureAd 'Microsoft.ApiManagement/service/apis/policies@2021-08-01' = {
  name: '${ApiManagementApiAzureAd.name}/policy'
  dependsOn: [
    ApiManagementNamedValueAuthServer
    ApiManagementNamedValueClientId
    ApiManagementNamedValueScope
    ApiManagementNamedValueClientSecret
  ]
  properties: {
    value: '<policies>\r\n  <inbound>\r\n    <base />\r\n    <send-request ignore-error="true" timeout="20" response-variable-name="bearerToken" mode="new">\r\n      <set-url>{{${apim_nv_authserver}}}</set-url>\r\n      <set-method>POST</set-method>\r\n      <set-header name="Content-Type" exists-action="override">\r\n        <value>application/x-www-form-urlencoded</value>\r\n      </set-header>\r\n      <set-body>\r\n          @{\r\n              return "client_id={{${apim_nv_clientid}}}&amp;scope={{${apim_nv_scope}}}&amp;client_secret={{${apim_nv_clientsecret}}}&amp;grant_type=client_credentials";\r\n          }\r\n        </set-body>\r\n    </send-request>\r\n    <set-header name="Authorization" exists-action="override">\r\n      <value>\r\n          @("Bearer " + (String)((IResponse)context.Variables["bearerToken"]).Body.As&lt;JObject&gt;()["access_token"])\r\n      </value>\r\n    </set-header>\r\n    <set-header exists-action="delete" name="Ocp-Apim-Subscription-Key" />\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>'
    format: 'xml'
  }
}

resource ApiManagementNamedValueAuthServer 'Microsoft.ApiManagement/service/namedValues@2021-08-01' = {
  name: '${ApiManagement.name}/${apim_nv_authserver}'
  properties: {
    displayName: apim_nv_authserver
    value: '${environment().authentication.loginEndpoint}${aad_tenantid}/oauth2/v2.0/token'
  }
}

resource ApiManagementNamedValueClientAppId 'Microsoft.ApiManagement/service/namedValues@2021-08-01' = {
  name: '${ApiManagement.name}/${apim_nv_clientappid}'
  properties: {
    displayName: apim_nv_clientappid
    value: aad_appid_client
  }
}

resource ApiManagementNamedValueScope 'Microsoft.ApiManagement/service/namedValues@2021-08-01' = {
  name: '${ApiManagement.name}/${apim_nv_scope}'
  properties: {
    displayName: apim_nv_scope
    value: 'api://${aad_appid_backend}/.default'
  }
}

resource ApiManagementNamedValueClientSecret 'Microsoft.ApiManagement/service/namedValues@2021-08-01' = {
  name: '${ApiManagement.name}/${apim_nv_clientsecret}'
  dependsOn: [
    KeyVaultAccessPolicies
  ]
  properties: {
    displayName: apim_nv_clientsecret
    keyVault: {
      secretIdentifier: 'https://${kv_name}${environment().suffixes.keyvaultDns}/secrets/${kvsecret_name_clientsecret}'
    }
    secret: true
  }
}
```

## Configuration - Basic auth username and password for backend <a name="config-basicauth"></a>

- Use `<authentication-basic>` to pass a basic authentication request to the backend
- The username and password is from Named Value. The password is stored in Key Vault.
- Delete the subscription key before the request is passed through the backend.

**Policy example**

| Named values        | Comments                               |
|---------------------|----------------------------------------|
| basicAuthUserName   | Username for Basic Authentication      |
| basicAuthPass       | Password for Basic Authentication      |

```
<policies>
  <inbound>
      <base />
      <authentication-basic username="{{basicAuthUserName}}" password="{{basicAuthPass}}" />
      <set-header name="Ocp-Apim-Subscription-Key" exists-action="delete" />
  </inbound>
  <backend>
      <base />
  </backend>
  <outbound>
      <base />
  </outbound>
  <on-error>
      <base />
  </on-error>
</policies>
```

- Policy depends on the Named Values of username and password. Use `dependsOn` to specify the deployment order.

[bicep example](https://github.com/koheikawata/api-management-sample/blob/main/src/bicep/azuredeploy_subscription.bicep)
```
resource ApiManagementPolicyBasic 'Microsoft.ApiManagement/service/apis/policies@2021-08-01' = {
  name: '${ApiManagementApiBasic.name}/policy'
  dependsOn: [
    ApiManagementNamedValueBasicAuthName
    ApiManagementNamedValueBasicAuthPass
  ]
  properties: {
    value: '<policies>\r\n  <inbound>\r\n    <base />\r\n    <authentication-basic username="{{${apim_nv_basicauthuser}}}" password="{{${apim_nv_basicauthpass}}}" />\r\n  <set-header exists-action="delete" name="Ocp-Apim-Subscription-Key" />\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>'
    format: 'xml'
  }
}

resource ApiManagementNamedValueBasicAuthName 'Microsoft.ApiManagement/service/namedValues@2021-08-01' = {
  name: '${ApiManagement.name}/${apim_nv_basicauthuser}'
  properties: {
    displayName: apim_nv_basicauthuser
    value: basic_auth_user
  }
}

resource ApiManagementNamedValueBasicAuthPass 'Microsoft.ApiManagement/service/namedValues@2021-08-01' = {
  name: '${ApiManagement.name}/${apim_nv_basicauthpass}'
  dependsOn: [
    KeyVaultAccessPolicies
  ]
  properties: {
    displayName: apim_nv_basicauthpass
    keyVault: {
      secretIdentifier: 'https://${kv_name}${environment().suffixes.keyvaultDns}/secrets/${kvsecret_name_basic_pass}'
    }
    secret: true
  }
}
```

## Configuration - Certificate for backend <a name="config-certificate"></a>

- Use `<authentication-certificate>` policy and points to the certificate uploaded to the Key Vault and linked to Certificate in API Management.
- Delete the subscription key before the request is passed through the backend.
- The Certificate in API Management refers to the one stored in the Key Vault.

**Policy example**

| Certificate ID      | Comments                               |
|---------------------|----------------------------------------|
| apicert        | Certificate ID registered in API Management |

```
<policies>
    <inbound>
        <base />
        <authentication-certificate certificate-id="apicert" />
        <set-header name="Ocp-Apim-Subscription-Key" exists-action="delete" />
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>
```

- In bicep deployment, the Policy depends on the Certificate registered in API Management. Use `dependsOn` to specify the deployment order.
- The policy points to `apim_certificate_id` which is Certificate ID in API Management.
- In bicep deployment, the API Management needs an accesspolicies of the Key Vault. And the deployment order should be Key Vault Access Policy, Certificate, and Policy.

[bicep example](https://github.com/koheikawata/api-management-sample/blob/main/src/bicep/azuredeploy_subscription.bicep)
```
resource ApiManagementPolicyCert 'Microsoft.ApiManagement/service/apis/policies@2021-08-01' = {
  name: '${ApiManagementApiCert.name}/policy'
  dependsOn: [
    ApiManagementCertificate
  ]
  properties: {
    value: '<policies>\r\n  <inbound>\r\n    <base />\r\n    <authentication-certificate certificate-id="${apim_certificate_id}" />\r\n    <set-header exists-action="delete" name="Ocp-Apim-Subscription-Key" />\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>'
    format: 'xml'
  }
}

resource ApiManagementCertificate 'Microsoft.ApiManagement/service/certificates@2021-08-01' = {
  name: '${ApiManagement.name}/${apim_certificate_id}'
  dependsOn: [
    KeyVaultAccessPolicies
  ]
  properties: {
    keyVault: {
      secretIdentifier: 'https://${kv_name}${environment().suffixes.keyvaultDns}/secrets/${kvcert_name_api}'
    }
  }
}

resource KeyVaultAccessPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2021-10-01' = {
  name: '${KeyVault.name}/add'
  properties: {
    accessPolicies: [
      {
        tenantId: tenant_id
        objectId: ApiManagement.identity.principalId
        permissions: {
          secrets: [
            'get'
            'list'
          ]
          certificates: [
            'get'
            'list'
          ]
        }
      }
    ]
  }
}
```

# Sample request <a name="sample-request"></a>

**Powershell**
```
$subscriptionKey = "{Subscription key}"
$uri = "https://{API Management name}.azure-api.net/AzureAdAuth/Weatherforecast/RequireAuth"
$headers = @{
  "Ocp-Apim-Subscription-Key" = $subscriptionKey
}
Invoke-RestMethod -Uri $uri -Method Get -Headers $headers
```

**bash**
```
curl -H "Ocp-Apim-Subscription-Key: {Subscription key}" \
  https://{API Management name}.azure-api.net/AzureAdAuth/Weatherforecast/RequireAuth
```