Delivering the Expected - Solteq Developer Blog

Utilizing Azure APIM to improve security of your system

APIM Castle Title “Banners icon taken and modified from Stockio](https://www.stockio.com/free-icon/arthur-icon-set-banner-castle)

Welcome in my second article about APIM. If you didn’t read the previous one and you do not know the APIM, I will suggest going and read it first. As quick reminder, below figure depicts the end state of our services from previous article:

API split

In this series

  1. APIM Introduction
  2. APIM Security
  3. Ways of APIM Deployment automation

Background

The main reason we started to investigate APIM were the security features it provides. We point our focus to this part of the service. In this article I will go through the security options we used to protect backend services. By the same token, our main concern was how to efficiently provide and handle two different authentication strategies for both Web Clients and Hardware Devices. We put our hope in APIM to provide us a solution to this not straightforward task.

Hiding backends

I will start simple by hiding the backend information from the users of your apis. The main reason you want to do that is to limit the information about technologies and servers used by your application so it will be more difficult for the attacker to penetrate your software.

The minimal policy, suggested by Microsoft, is to add following rules to outbound processing:

<outbound>
        <set-header name="X-Powered-By" exists-action="delete" />
        <set-header name="X-AspNet-Version" exists-action="delete" />
        <find-and-replace from="://YOUR-SITE.azurewebsites.net" to="://YOUR-APIM.azure-api.net"/>
        <base />
</outbound>

First two rules will remove headers that store information about server and asp net version. Same way you can remove another potentially risky header from response. The last rule replaces the any occurrence of backend address from the response with a relative apim address.

The aim of those rules is to hide the information about technology stack used by your services and to remove any trace of original service address.

Authentication

Web Clients

Previous step was cosmetic change. Now it’s time for fireworks! As already mentioned, we have two types of clients: Web Application and Hardware Device.

Web Client use OAuth JWT tokens. After successfully login, user gets his access token. It is then attached to every request in authorization header. Then, each service is responsible for checking validity of the token and authorize user rights to access different resources. We can move this check logic to APIM using proper policies. Now, each request to ClientApi in APIM will trigger token verification. Ideally, we can then remove that logic from backends. This rule can look like the one below:

<inbound>
        <validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="@((string)context.LastError.Message)" require-scheme="Bearer" require-signed-tokens="true">
            <openid-config url="https://{authorization_server_url}/.well-known/openid-configuration" />
            <required-claims> <!-- list of required claims -->
                <claim name="clientId" /> <!-- user must belong to some company -->
                <claim name="scope" match="any">
                    <value>ManagementApi</value> <!-- name of the backend service as registered in OpenId server -->
                </claim>
            </required-claims>
        </validate-jwt>
</inbound>

More info:

Hardware devices

Contrarily, for devices, we can’t use JWT token validation as it is too complicated for machine clients. We choose an authentication based on certificates. Each device has its own SSL certificate which is used to access the services. To authorize device, we need to check if the certificate is valid and if certificate matches the device metadata stored in our DB. This can be achieved in APIM for example in following way:

<inbound>
        <choose>
            <when condition="@(context.Request.Certificate == null || !context.Request.Certificate.VerifyNoRevocation())">
                <return-response>
                    <set-status code="403" reason="Forbidden. Invalid client certificate." />
                </return-response>
            </when>
        <otherwise>
        <!--  further request processing -->
        </otherwise>
</inbound>
<otherwise>
        <!-- get the thumbprint of certificate -->
        <set-variable name="thumbprint" value="@(context.Request.Certificate.Thumbprint)" />
        <!-- send request to a service that will verify device id and certificate -->
        <!-- our response will be saved in response-variable-name -->
        <send-request mode="new" response-variable-name="deviceCredentials" timeout="20" ignore-error="false">
                <set-url>@("https://{device_auth_service_url}?certificateThumbprint=" + ((string)(context.Variables["thumbprint"])) + "&deviceId=" + ((string)(context.Request.MatchedParameters["deviceId"])))</set-url>
                <set-method>GET</set-method>
        </send-request>
        <choose>
                <!-- when we got 200 response - we can auth the device to backend services -->
                <when condition="@(context.Response.StatusCode == 200)">
                        <!-- setting additional header information -->
                        <set-header name="x-request-context-data" exists-action="override">
                            <value>@($"deviceId={(string)(((IResponse)context.Variables["deviceCredentials"]).Body.As<JObject>(preserveContent: true)["deviceId"])};" +
                                $"certificateThumbprint={(string)(((IResponse)context.Variables["deviceCredentials"]).Body.As<JObject>(preserveContent: true)["certificateThumbprint"])};" +
                                $"clientId={(string)(((IResponse)context.Variables["deviceCredentials"]).Body.As<JObject>()["clientId"])}")</value>
                        </set-header>
                </when>
                <otherwise>
                        <!-- if something went wrong, certificate did not match robot etc. - return 403 or 401 -->
                        <return-response>
                            <set-status code="403" reason="Forbiden. Certificate was not recognized." />
                        </return-response>
                </otherwise>
        </choose>
</otherwise>

The policy is not straightforward, and I will advise to read linked materials to fully understand it. The most interesting part of it is that we were able to include a custom authentication service in between Device Client and Microservice layer that will be used by APIM. Personally, I think that is great if not amazing thing to have :). Also, there is no track about this service anywhere, so the possible attacker will not be able to interact with it.

Let’s update our diagram with auth layer to show before: auth before

and after state: auth after *note: in before state, we did not have working device authentication.

Securing with SSL

The final step that I will touch in this article is to use mutual certificate authentication between APIM and backends. Enabling this feature will restrict access to our backend to request that are signed with internal APIM certificate. In other words, we are adding a certificate to APIM to sign the requests forwarded to backend services. Backends are responsible to verify if the request has certificate (that is done by azure) and if certificate is the one and only one that we expect (this is the part we need to implement).

Remember that since now, you need to use this certificate every time you want to access your backend - that includes the use of Postman. It is worth to disable this layer for local debugging.

Enabling this feature in APIM is very simple. After uploading a certificate, you need to add following line in api inbound policy:

<authentication-certificate thumbprint="EB92765BD7DB89CB44E21676E73DCE7B8D59217E" />

Also, the webservice needs to have request client certificate flag enabled.

Note that after switching above option, if you are hosting your backend and frontend under same azure app service - you will be forced to use that certificate even when opening website. Usually that forces you host the two separately.

There are more advanced options to secure the service - like virtual networks. We planned to explore that later. Find out more here

Our final architecture can be depicted in below diagram: final

Wrap up

I hope you manage to read this story to the end. I tried to describe the process of improvements without getting much into details. For more information you can go to the linked sites I provided in each section. At the end, with minimal coding, we manage to improve security of our services in significant way. What is more, we enforce some rules that all other services that will be created will need to implement. Additionally, all security concerns are kept in single place, unloading the development effort of new microservices.




Join us?

See what we have to offer - Careers