Welcome to today’s blog.
Today I will be showing how you can use .NET Core to create a functioning API Gateway for your microservice APIs.
Before I show how an API Gateway is configured, I will explain what an API Gateway is. I will then introduce an open-source API Gateway, Ocelot, that we can use as a starter for experimentation and production usage within API Gateways.
I will then show how to configure API Gateway routing for both unauthenticated and authenticated requests.
What is an API Gateway?
An API Gateway is an API routing service that serves primarily to route external requests from our client application (including web and mobile devices). This is identical to what a reverse proxy does. The benefit is to decouple a client application from the microservice APIs.
In addition, we can use the API Gateway to authenticate users from the external application to our API services using a built-in identity server or a custom federated identity server. A diagram depicting our architecture is shown below:
In the implementation discussion I will assume that you have already implemented two basic Web API services that will be routed within our API Gateway. In a previous post I showed how to implement a basic Web Service API using .NET Core.
If our Web API service has already implemented support for validation of JWT tokens at the method (declarative) level, then the API implementation does not need any further changes to accommodate forwarding of JWT security from the API Gateway.
Within our API Gateway API, we will be using open-source middleware for our gateway functionality. There are numerous API Gateway products on the markets, both free open-source and commercial licensed such as:
API Gee
Nginx
Ocelot
Azure API Gateway
I will be using the Ocelot open-source gateway to provide the API gateway middleware. To obtain the Ocelot middleware we install it as a Nuget package.
Create the application as a .NET Core Web API application.
The first most basic feature of the API gateway we will implement is for routing unauthenticated requests. In the next post I will be showing how to integrate authentication using JWT tokens into our API gateway.
Routing of Unauthenticated Upstream Requests
The Ocelot gateway middleware requires the generation of a configuration file so that routing and general settings can be configured.
A JSON configuration file will need to be setup within Program.cs as shown:
using System.IO;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace BookLoan.Gateway.API
{
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
var builder = WebHost.CreateDefaultBuilder(args);
builder.ConfigureServices(s => s.AddSingleton(builder))
.ConfigureAppConfiguration(ic =>
ic.AddJsonFile("ocelot.json"))
.UseStartup<Startup>();
return builder;
}
}
}
You will also need to create a configuration file named Ocelot.json within the project folder.
The configuration file has two main keys which need population:
{
"Routes": [],
"GlobalConfiguration": {}
}
The Routes key is where we populate the routing of our API microservices.
To configure our API microservice re-routing in the Routes key, we set the DownStreamPathTemplate, which is the API path of each destination microservice API that is accessed using HTTP.
We then set the DownstreamHostAndPorts, which is the destination server hosting the API service and the port.
We also set the downstream path, which is the response we get from our API service in the form of a GET, POST or PUT http method.
We also need to set the UpStreamPathTemplate for our gateway upstream HTTP requests.
Routing of Authenticated Upstream Requests
To authenticate the request, we will need to set the AuthenticationOptions key. In particular, the AuthenticationProviderKey will store the name of our identity key.
"ReRoutes": [
{
"DownstreamPathTemplate": "/bookloan.catalog.api/api/{everything}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 80
}
],
"UpstreamPathTemplate": "/book/{everything}",
"UpstreamHttpMethod": [ "POST", "PUT", "GET" ]
},
{
"DownstreamPathTemplate": "/bookloan.loan.api/api/{everything}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 80
}
],
"UpstreamPathTemplate": "/loan/{everything}",
"UpstreamHttpMethod": [ "POST", "PUT", "GET" ]
}
],
"GlobalConfiguration": {
"BaseUrl": "http://localhost:5000"
}
}
To enable the Ocelot gateway, we call the AddOcelot() extension method in ConfigureSevices():
public void ConfigureServices(IServiceCollection services)
{
…
services.AddOcelot();
…
}
We also call the UseOcelot() extension method in Configure():
public void Configure(IApplicationBuilder app, IHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseOcelot().Wait();
}
The launch setttings.json will require the application URL matching the same URL value configured within the BaseUrl key defined within the GlobalConfiguration key of Ocelot.json.
"BookLoan.Gateway.API": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:5000"
},
Run the gateway application for the defined launch settings. In POSTMAN, test the backend downstream API:
This shows the downstream URL: http://localhost/BookLoan.Catalog.API/api/Book/AllBooks
responds successfully.
The browser opens to URL: http://localhost:5000.
Now type in an upstream URL:
http://localhost:5000/book/allbooks The browser response should be:
In the console, you will see the following output showing the initiation of the upstream request URL:
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/1.1 GET http://localhost:5000/book/allbooks
dbug: Ocelot.Errors.Middleware.ExceptionHandlerMiddleware[0]
requestId: 0HM6CSL16O3NA:00000001, previousRequestId: no previous request id, message: ocelot pipeline started
dbug: Ocelot.DownstreamRouteFinder.Middleware.DownstreamRouteFinderMiddleware[0]
requestId: 0HM6CSL16O3NA:00000001, previousRequestId: no previous request id, message: Upstream url path is /book/allbooks
The following output shows the matching of the downstream request URL:
dbug: Ocelot.DownstreamRouteFinder.Middleware.DownstreamRouteFinderMiddleware[0]
requestId: 0HM6CSL16O3NA:00000001, previousRequestId: no previous request id, message: downstream templates are /bookloan.catalog.api/api/book/{everything}
In the following output you will notice the check for authentication shows the matched downstream API method requires no authentication:
info: Ocelot.Authentication.Middleware.AuthenticationMiddleware[0]
requestId: 0HM6CSL16O3NA:00000001, previousRequestId: no previous request id, message: No authentication needed for /book/allbooks
info: Ocelot.Authorisation.Middleware.AuthorisationMiddleware[0]
requestId: 0HM6CSL16O3NA:00000001, previousRequestId: no previous request id, message: /bookloan.catalog.api/api/book/{everything} route does not require user to be authorised
The following output shows the HTTP request on the downstream API method:
dbug: Ocelot.DownstreamUrlCreator.Middleware.DownstreamUrlCreatorMiddleware[0]
requestId: 0HM6CSL16O3NA:00000001, previousRequestId: no previous request id, message: Downstream url is http://localhost/bookloan.catalog.api/api/book/allbooks
info: Ocelot.Requester.Middleware.HttpRequesterMiddleware[0]
requestId: 0HM6CSL16O3NA:00000001, previousRequestId: no previous request id, message: 200 (OK) status code, request uri: http://localhost/bookloan.catalog.api/api/book/allbooks
The final output in the request pipeline shows the JSON response and status code:
dbug: Ocelot.Requester.Middleware.HttpRequesterMiddleware[0]
requestId: 0HM6CSL16O3NA:00000001, previousRequestId: no previous request id, message: setting http response message
dbug: Ocelot.Responder.Middleware.ResponderMiddleware[0]
requestId: 0HM6CSL16O3NA:00000001, previousRequestId: no previous request id, message: no pipeline errors, setting and returning completed response
dbug: Ocelot.Errors.Middleware.ExceptionHandlerMiddleware[0]
requestId: 0HM6CSL16O3NA:00000001, previousRequestId: no previous request id, message: ocelot pipeline finished
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished in 185.3507ms 200 application/json; charset=utf-8
This shows our gateway routing for HTTP requests works as expected with a successful response.
The Ocelot API gateway is a useful open-source library for provisioning an API gateway to your microservices. With some experimentation, you have seen how to setup routing or both authenticated and unauthenticated HTTP requests. From here we can setup more complex API routing. As I mentioned earlier, there are additional API gateway products you can use which can be configured similarly for your microservices.
That’s all for today’s post.
In the next post I will be showing how to integrate authentication into our API gateway.
I hope you found this post useful and informative.
Andrew Halil is a blogger, author and software developer with expertise of many areas in the information technology industry including full-stack web and native cloud based development, test driven development and Devops.