DEV Community

Kohei Kawata
Kohei Kawata

Posted on • Edited on

Azure API Management authentication - Part.1

Summary

In this article, I am going to share how Azure API Management authentication works. The sample code 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.

TOC

Architecture

  • 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

  • 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

  • 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

Subscription key validation

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

Configuraiton - Subscription key

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

Bicep example



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


Enter fullscreen mode Exit fullscreen mode

Configuraiton - Azure AD token for backend

  • 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>


Enter fullscreen mode Exit fullscreen mode
  • 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



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


Enter fullscreen mode Exit fullscreen mode

Configuration - Basic auth username and password for backend

  • 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>


Enter fullscreen mode Exit fullscreen mode
  • Policy depends on the Named Values of username and password. Use dependsOn to specify the deployment order.

bicep example



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


Enter fullscreen mode Exit fullscreen mode

Configuration - Certificate for backend

  • 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>


Enter fullscreen mode Exit fullscreen mode
  • 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



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


Enter fullscreen mode Exit fullscreen mode

Sample request

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


Enter fullscreen mode Exit fullscreen mode

bash



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


Enter fullscreen mode Exit fullscreen mode

Top comments (0)