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 ishttps://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.
- 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
- 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
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.
Configuraiton - Subscription key
Requiring the subscription key is configured for each API level. In bicep, you need to turn subscriptionRequired: true
for all APIs.
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
- 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.
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}}}&scope={{${apim_nv_scope}}}&client_secret={{${apim_nv_clientsecret}}}&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<JObject>()["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
- 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.
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
- 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.
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
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
Top comments (0)