Let's setup an API gateway using the Azure API Management service with a nice architecture.
An API gateway is positioned between your APIs and the Internet. You can control how the APIs are exposed or limit the usage per subscription through the Azure portal.
It is a native Azure SaaS (software as a service) which brings nice pros;
- API documentation
- Rate limiting access
- Health monitoring
- Modern formats like JSON
- Connections to any API
- Built-in caching
- Network tracing
Yes, the circuit breaker policy is not implemented yet. You can build your own API gateway with a circuit breaker using third-party libraries like Ocelot and Polly with Quality of Service configured but does it worth to go down to a PaaS (platform as a service) instead of a SaaS?
- Sign into the Azure portal.
- Create a resource through Integration, and then API Management.
- Give a globally unique name to your resource.
- Select Consumption (99.9SLA, %) as the pricing tier because this serverless plan is much faster to create for an ad-hoc testing.
- Click Create.
- Deployment will start and it may take several minutes.
When the deployment is complete you will get a notification email to the address you provided as an administrator. Once it is deployed we import our first API.
- Sign into the Azure portal.
- Go to All Resources, and then select your API gateway.
- Under API Management, click APIs
- Choose your specification. If you are using Swashbuckle library in your API that means you already have OpenAPI specification.
- On the Create from OpenAPI specification page, paste the swagger JSON URL of your API. The other fields will be populated according to your API.
- Click Create.
Now, you can test the gateway through the Test tab on the API details page.
A subscription is like authenticating a client with a public key which can be transferred through in the headers or in the query string. The subscription can be scoped to All APIs, a single API or a group of APIs (called a product).
The unique subscription keys can be regenerated at any time. Every subscription has two keys, a primary and a secondary to avoid downtime.
The default header name is
Ocp-Apim-Subscription-Key, and the default query string is
subscription-key. If the key is not passed in the header, or as a query string in the URL, you'll get a 401 Access Denied response from the API gateway.
In Azure API Management, administrators can use policies to alter the behavior of APIs through configuration. Policies execute at four different times:
- Inbound: These policies execute when a request is received from a client.
- Backend: These policies execute before a request is forwarded to a managed API.
- Outbound: These policies execute before a response is sent to a client.
- On-Error: These policies execute when an exception is raised.
There is one more scope in addition to subscription scopes for policies and it is operation policy scope. We can determine order of the policies. If we place
<base /> tag before a policy that means higher policies will be applied first and vice versa.
- Check HTTP header
- Limit call rate by subscription
- Restrict caller IP's
- Convert XML to JSON
- Rewrite URL
By caching the compiled responses we can reduce processing time in our APIs and respond faster. We can add an outbound policy to cache the responses and an inbound policy to check if there is a cached response for the current request. You can see these two policies in the example below which is using a
vary-by-query-parameter tag to store separate responses per id in a query string:
<policies> <inbound> <base /> <cache-lookup vary-by-developer="false" vary-by-developer-groups="false" downstream-caching-type="none" must-revalidate="true" caching-type="internal"> <vary-by-query-parameter>id</vary-by-query-parameter> </cache-lookup> </inbound> <backend> <base /> </backend> <outbound> <cache-store duration="60" /> <base /> </outbound> </on-error> <base /> </on-error> </policies>
vary-by-developer attribute is separating responses according to the subscription key.
As you can already notice, the caching type for this example is internal. We can use an external caching service as well.
- Create an Azure Cache for Redis resource.
- Go to your API Management service and click External cache under Settings and then click + Add.
- Choose your Redis cache instance and a location to use from. The other fields will be populated.
- Click Save.
- Go to APIs under API Management an then select the API and click the pencil next to the cache-lookup to edit the policy.
- Change caching type from Internal to External
You can test your API in the Test tab to see if it is giving unchanged results.
ASP.NET add a
X-Powered-By: ASP.NET header to our APIs by default but this could allow a malicious user to attempt to exploit any bugs known for the technology stack. We can remove this header from a response by adding
set-header policy to outbound as in the example below;
<outbound> <set-header name="X-Powered-By" exists-action="delete" /> <base /> </outbound>
If we build an API with the HATEOAS constraint, that means we have links in the responses. Since the gateway is overriding the URLs, we may need to replace the links in the response body. We can do it by adding
<redirect-content-urls /> tag to the
<outbound> element as below;
<outbound> <set-header name="X-Powered-By" exists-action="delete" /> <redirect-content-urls /> <base /> </outbound>
If we place the
rate-limit-by-key tag inside the
inbound element, it will limit the call rate by subscription. A subscription can call this API 10 times in 60 seconds and only the successful responses are counted.
<inbound> <rate-limit-by-key calls="10" renewal-period="60" increment-condition="@(context.Response.StatusCode == 200)" counter-key="@(context.Subscription.Id)"/> <base /> </inbound>
If we want to use certificate authentication in our API gateway, we can validate it by an inbound policy.
- Under Settings, click Custom domains in your API Management service.
- Toggle Yes for the Request client certificate option, and then click Save.
- Replace the
<inbound>node of the policy file with the following XML, while replacing
desired-thumbprintpart with your certificate thumbprint;
<inbound> <choose> <when condition="@(context.Request.Certificate == null || context.Request.Certificate.Thumbprint != "desired-thumbprint")" > <return-response> <set-status code="403" reason="Invalid client certificate" /> </return-response> </when> </choose> <base /> </inbound>
Please, write the policies that you find useful in the comments.