Sunday, December 08, 2019

Use Azure Functions to control access to Azure API Management APIs


In some custom scenarios the authorization logic can be implemented using advanced Azure API Management policies (https://docs.microsoft.com/en-us/azure/api-management/api-management-access-restriction-policies), but  this option is tightly coupled to the requirement to maintain access control logic within Azure API Management policy as Azure API Management asset, which may lead to complicated CI/CD strategies and processes and so on).

Another approach is to “outsource” the access control logic to an external service, maintain it separately from Azure API Management instance and use it from Azure API management inbound policy (https://docs.microsoft.com/en-us/azure/api-management/api-management-howto-policies) to control access to APIs or single methods using appropriated policy scope (https://docs.microsoft.com/en-us/azure/api-management/api-management-howto-policies#apply-policies-specified-at-different-scopes).

The idea behind this approach:

  •         Implement an access control [micro]service to make decision to allow or to deny access control to certain Azure API Management API or method (for example, Azure Function),
  •      Incoming request to an API method is received by Azure API Management instance,
  •         Inbound policy of the API or method extract from request information, essential to allow or to deny access to the API or method,
  •         Inbound policy issues request to the access control service (s. above) using information, extracted from request,
  •         Access control service returns response with either allowed or denied permission,
  •         Inbound policy uses the response from access control service to deny access or to pass it thru to the backend.
The flow of events in this diagram:







      1 An  API client sends a request to an API method (including information for access control) in Azure API Management instance.
2 The request is intercepted and analyzed by inbound policy, Azure API Management instance sends another request to an external access control service.
3 Access control service responds to Azure API Management instance with information about allowed or denied access.
4 If access is allowed, inbound policy passes the request thru the chain to backend service
             4* If access is denied, according response is returned to the API client and request is not passed to backend.

Let’s look at a sample implementation of this approach.

Assume an access control service implemented as Azure Function:

#r "Newtonsoft.Json"
using System.Net;
using System.Text;
using Newtonsoft.Json;

//
// Sample implementation of access control service
// for Azure APi Management API and method protection
// making decision to allow or deny access based on
// custom token value and API name.
//
// This function is intented to use from Azure API Management
// inbound policy to implement fine granulated custom access
// control to APIs and single methods
//
// HTTP GET request parameters:
// - token: custom access token string
// - API: API name
//
// Response JSON
// { "permitted": true | false}
//

public static async Task Run(HttpRequestMessage req, TraceWriter log)
{
    log.Info("C# HTTP trigger function processed a request.");

    // parse query parameter - access token value
    string token = req.GetQueryNameValuePairs()
        .FirstOrDefault(q => string.Compare(q.Key, "token", true) == 0)
        .Value;

    // parse query parameter - API name
    string api = req.GetQueryNameValuePairs()
        .FirstOrDefault(q => string.Compare(q.Key, "API", true) == 0)
        .Value;

    //
    // This primitive access control logic must be replaced by custom
    //
    var access = new {permitted = (token=="permittedTokenValue"?true:false)};
    var jsonObject = JsonConvert.SerializeObject(access);
   
    return new HttpResponseMessage(HttpStatusCode.OK) {
        Content = new StringContent(jsonObject, Encoding.UTF8, "application/json")
    };
}


This primitive function can be used as custom access control service using HTTP GET requests with two query parameters: custom token and API name:
https:///api/CheckAccess?token=mytoken&API=test
getting JSON response either
{"permitted":false}
Or
{"permitted":true}
Assume for simplicity the API client sends custom access token information in a custom HTTP header named “token” (in real life you may want to extract access control specific information from JWT token as referenced above).
The inbound policy of an API method with customized access control looks then like


<inbound>
       
        <set-variable name="token" value="@(context.Request.Headers.GetValueOrDefault("token",""))" />
       
        <send-request mode="new" response-variable-name="access" timeout="20" ignore-error="true">
           
            <set-url>@("https://mbecho.azurewebsites.net/api/CheckAccess?token="+(string)context.Variables["token"]+"&API=protectedAPI")</set-url>
            <set-method>GET</set-method>
            <set-header name="Authorization" exists-action="override">
                <value>whatever</value>
            </set-header>
            <set-header name="Content-Type" exists-action="override">
                <value>application/x-www-form-urlencoded</value>
            </set-header>
            <set-body />
        </send-request>
        <choose>
           
            <when condition="@((bool)((IResponse)context.Variables["access"]).Body.As()["permitted"] == false)">
               
                <return-response response-variable-name="existing response variable">
                    <set-status code="401" reason="Unauthorized" />
                </return-response>
            </when>
        </choose>
       
        <base />

    </inbound>

Test the protected API method with “permittedTokenValue” succeeds

…and fails with any other values

The “Trace” tab in Test pane of Azure API Management section in Azure portal contains detailed info about flow of events and request and policy processing. The additional latency caused by use of an external access control service in this sample is under 50 msecs