Azure API Management authentication - Part.2
Published: 2022-04-18 13:36:22

# Summary
Following the article Part.1, I would 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.2, I would talk about the Gateway Validation pattern.

- [Azure API Management authentication - Part.1](https://dev.to/koheikawata/azure-api-management-authentication-part1-1198)
- [Azure API Management authentication - Part.3](https://dev.to/koheikawata/azure-api-management-authentication-part3-nbp)

# TOC
- [Architecture](#architecture)
- [Gateway validation](#gateway-validation)
  - [Configuraiton - Azure AD token validation](#config-azuread)
  - [Configuraiton - Basic auth validation](#config-basicauth)
  - [Configuration - Client certificate validation](#config-certificate)
- [Sample request](#sample-request)

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

As mentioned in [Part.1](https://dev.to/koheikawata/azure-api-management-authentication-part1-1198), the APIs that have different types of authentication are behind API Management.

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

# Gateway validation <a name="gateway-validation"></a>

- API Management Gateway validates each type of credential, Azure AD token, Basic Authentication username and password, and Client certificate.
- For Azure AD and Basic Authentication, the credentials attached on the request header are passed through the gateway to the backend.
- For Client Certificate Authentication, it is difficult to pass through to the backend. Once a client certificate is validated at the API Management Gateway, another client certificate is retrieved from Azure Key Vault and attached to the backend request.

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

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

- It uses `<validate-jwt>` and sees if the header includes the right claims.
- It checks `aud` and `appid` claims.

**Policy example**

| Named values        | Comments                               |
|---------------------|----------------------------------------|
| aadTenantId         | Azure AD tenant ID                     |
| backendAppId        | Azure AD application ID of client app  |
| clientAppId         | Azure AD applicaiton ID of backend app |

```
<policies>
    <inbound>
        <base />
        <validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid.">
            <openid-config url="https://login.microsoftonline.com/{{aadTenantId}}/.well-known/openid-configuration" />
            <audiences>
                <audience>api://{{backendAppId}}</audience>
            </audiences>
            <required-claims>
                <claim name="appid" match="all">
                    <value>{{clientAppId}}</value>
                </claim>
            </required-claims>
        </validate-jwt>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>
```

- In Bicep deployment, the Policy depends on Named Values. It uses `dependsOn` to specify the deployment order.

[Bicep example](https://github.com/koheikawata/api-management-sample/blob/main/src/bicep/azuredeploy_gateway.bicep)

```
resource ApiManagementPolicyAzureAd 'Microsoft.ApiManagement/service/apis/policies@2021-08-01' = {
  name: '${ApiManagementApiAzureAd.name}/policy'
  dependsOn: [
    ApiManagementNamedValueClientAppId
    ApiManagementNamedValueBackendAppId
    ApiManagementNamedValueAadTenantId
  ]
  properties: {
    value: '<policies>\r\n  <inbound>\r\n    <base />\r\n    <validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid.">\r\n      <openid-config url="${environment().authentication.loginEndpoint}{{${apim_nv_aadtenantid}}}/.well-known/openid-configuration" />\r\n      <audiences>\r\n        <audience>api://{{${apim_nv_backendappid}}}</audience>\r\n      </audiences>\r\n      <required-claims>\r\n        <claim name="appid" match="all">\r\n          <value>{{${apim_nv_clientappid}}}</value>\r\n        </claim>\r\n      </required-claims>\r\n    </validate-jwt>\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 ApiManagementNamedValueClientAppId 'Microsoft.ApiManagement/service/namedValues@2021-08-01' = {
  name: '${ApiManagement.name}/${apim_nv_clientappid}'
  properties: {
    displayName: apim_nv_clientappid
    value: aad_appid_client
  }
}

resource ApiManagementNamedValueBackendAppId 'Microsoft.ApiManagement/service/namedValues@2021-08-01' = {
  name: '${ApiManagement.name}/${apim_nv_backendappid}'
  properties: {
    displayName: apim_nv_backendappid
    value: aad_appid_backend
  }
}

resource ApiManagementNamedValueAadTenantId 'Microsoft.ApiManagement/service/namedValues@2021-08-01' = {
  name: '${ApiManagement.name}/${apim_nv_aadtenantid}'
  properties: {
    displayName: apim_nv_aadtenantid
    value: aad_tenantid
  }
}
```

## Configuraiton - Basic auth validation <a name="config-basicauth"></a>

- API Management Gateway validates if an username and password on the request header is identical with those which registered as Named Values in API Management. The password is stored in Azure Key Vault and API Management refers to it.

**Policy example**

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

```
<policies>
    <inbound>
        <base />
        <set-variable name="user-pass" value="{{basicAuthUserName}}:{{basicAuthPass}}" />
        <check-header name="Authorization" failed-check-httpcode="401" failed-check-error-message="Not authorized" ignore-case="false">
            <value>@("Basic " + System.Convert.ToBase64String(Encoding.UTF8.GetBytes((string)context.Variables["user-pass"])))</value>
        </check-header>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>
```

- In Bicep deployment, the Policy depends on the Named Values registered in API Management. It uses `dependsOn` to specify the deployment order.

[Bicep example](https://github.com/koheikawata/api-management-sample/blob/main/src/bicep/azuredeploy_gateway.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   <set-variable name="user-pass" value="{{${apim_nv_basicauthuser}}}:{{${apim_nv_basicauthpass}}}" />\r\n    <check-header name="Authorization" failed-check-httpcode="401" failed-check-error-message="Not authorized" ignore-case="false">\r\n      <value>@("Basic " + System.Convert.ToBase64String(Encoding.UTF8.GetBytes((string)context.Variables["user-pass"])))</value>\r\n    </check-header>\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 - Client certificate validation <a name="config-certificate"></a>

- It requires to configure `negotiateClientCertificate: true` so API Management accepts a client certificate during TLS handshake. Without it, the Policy does not work for client certificate authentication.
- `negotiateClientCertificate: true` works for other request patterns without a client certificate. If you turn true/false back and forth, it takes an half hour or more to change the API Management configuration.
- It uses `<validate-client-certificate>` policy and validates the thumbprint stored in Azure Key Vault.
- `validate-trust` should turn to `false` for a self-signed certificate to work.
- `<authentication-certificate certificate-id="apicert" />` is needed to send a backend request with a certificate.

[Bicep example](https://github.com/koheikawata/api-management-sample/blob/main/src/bicep/azuredeploy_gateway.bicep)
```
properties: {
  hostnameConfigurations: [
    {
      type: 'Proxy'
      hostName: '${apim_name}.azure-api.net'
      negotiateClientCertificate: true
    }
  ]
}
```

**Policy example**

| Named values        | Comments                               |
|---------------------|----------------------------------------|
| certificateThumbprint | Thumbprint of Certificate uploaded   |

```
<policies>
    <inbound>
        <base />
        <validate-client-certificate validate-revocation="true" validate-trust="false" validate-not-before="true" validate-not-after="true" ignore-error="false">
            <identities>
                <identity thumbprint="{{certificateThumbprint}}" />
            </identities>
        </validate-client-certificate>
        <authentication-certificate certificate-id="apicert" />
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>
```

- Another policy pattern works too like below
- It does not work if you insert `!context.Request.Certificate.Verify()` or `context.Request.Certificate.VerifyNoRevocation()` because it uses a self-signed certificate and CA certificate is not uploaded to API Management.
```
<policies>
    <inbound>
        <base />
        <choose>
          <when condition="@(context.Request.Certificate == null || context.Request.Certificate.Thumbprint != "{Thumbprint uploaded in Key Vault}")">
              <return-response>
                  <set-status code="403" reason="Invalid client certificate" />
              </return-response>
          </when>
      </choose>
    </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_gateway.bicep)
```
resource ApiManagementPolicyCert 'Microsoft.ApiManagement/service/apis/policies@2021-08-01' = {
  name: '${ApiManagementApiCert.name}/policy'
  dependsOn: [
    ApiManagementNamedValueThumbprint
    ApiManagementCertificate
  ]
  properties: {
    value: '<policies>\r\n  <inbound>\r\n    <base />\r\n  <validate-client-certificate validate-revocation="true" validate-trust="false" validate-not-before="true" validate-not-after="true" ignore-error="false">\r\n      <identities>\r\n        <identity thumbprint="{{${apim_nv_thumbprint}}}" />\r\n      </identities>\r\n    </validate-client-certificate>\r\n   <authentication-certificate certificate-id="${apim_certificate_id}" />\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 ApiManagementNamedValueThumbprint 'Microsoft.ApiManagement/service/namedValues@2021-08-01' = {
  name: '${ApiManagement.name}/${apim_nv_thumbprint}'
  dependsOn: [
    KeyVaultAccessPolicies
  ]
  properties: {
    displayName: apim_nv_thumbprint
    keyVault: {
      secretIdentifier: 'https://${kv_name}${environment().suffixes.keyvaultDns}/secrets/${kvsecret_name_cert_thumbprint}'
    }
    secret: true
  }
}

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}'
    }
  }
}
```

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

**Powershell for AzureAdAuthAPI**
```
$clientSecret = "{Azure AD client app secret}"
$authorizeUri = "https://login.microsoftonline.com/{Azure AD tenant ID}/oauth2/v2.0/token"
$body = 'grant_type=client_credentials' + `
'&client_id={Azure AD client app ID}' + `
'&client_secret=' + $clientSecret + `
'&scope=api://{Azure AD backend app ID}/.default'
$token = (Invoke-RestMethod -Method Post -Uri $authorizeUri -Body $body).access_token
$Uri = "https://{API Management name}.azure-api.net/AzureAdAuth/Weatherforecast/RequireAuth"
$headers = @{
  "Authorization" = 'Bearer ' + $token
}
Invoke-RestMethod -Uri $Uri -Method Get -Headers $headers
```

**Powershell for BasicAuthAPI**
```
$basicAuthUsername = "{Basic Authenticaiton user}"
$basicAuthSecret = "{Basic Authenticaiton password}"
$bytes = [System.Text.Encoding]::ASCII.GetBytes($basicAuthUsername + ':' + $basicAuthSecret)
$authHeader = [Convert]::ToBase64String($bytes)
$Uri = "https://{API Management name}.azure-api.net/BasicAuth/Weatherforecast/RequireAuth"
$headers = @{
  "Authorization" = 'Basic ' + $authHeader
}
Invoke-RestMethod -Uri $Uri -Method Get -Headers $headers
```

**Powershell for CertificateAuthAPI**
```
$parameters = @{
    Method  = "GET"
    Uri     = "https://{API Management name}.azure-api.net/CertificateAuth/Weatherforecast/RequireAuth"
    Certificate  = (Get-PfxCertificate "{PFX file path}")
}
Invoke-RestMethod @parameters
```