Ocelot

Building API Gateway on .NET with Ocelot

In this tutorial, I’ll show you how to use Ocelot to create an API Gateway in .NET quickly and easily. You will learn how to install and configure Ocelot, and how to expose services through the API Gateway using this powerful open source tool. If you want to learn how to use Ocelot, this tutorial is for you!

In the code example we will create 3 microservices and an API Gateway with Ocelot, which will act as an intermediary between the clients and the services.

Let’s start with some theory? and then move on to the code

  1. What is an API Gateway and why is it useful?
  2. What is Ocelot and how does it work?
  3. Why use API gateway in microservices architecture?
  4. Microservices architecture example and API Gateway with Ocelot
  5. How to install and configure Ocelot in a .NET project
  6. How to expose services through the API Gateway using Ocelot
  7. How to implement some Ocelot features
    1. Request aggregation in API gateways with Ocelot
    2. Authentication to API gateways with Ocelot
    3. Authorization in API gateways with Ocelot
    4. Rate Limiting on API Gateways with Ocelot
    5. Delegating Handlers in API Gateways with Ocelot
    6. API Gateway Caching with Ocelot

What is an API Gateway and why is it useful?

An API Gateway is a layer used in microservices architectures to provide an abstraction layer between clients and services.

Essentially, it is a single entry point for directing traffic to various microservices and a central place where security, orchestration, monitoring, protocol transformation, and other non-functional requirements can be implemented. Instead of exposing each service individually through its own IP address and port, a single IP address and port is exposed for the API Gateway, which routes requests to the appropriate services.

What is Ocelot and how does it work?

Ocelot is an open source API gateway and routing tool for .NET that allows developers to create custom API gateways.

With Ocelot, we can implement features such as routing, authentication, authorization, throttling, load balancing, caching, response transformation, orchestration, monitoring, and more, simply and efficiently, making which improves the efficiency of the deployment and the scalability of the application.

Ocelot is used to provide a single, central entry point for requests to multiple microservices. It uses a configuration file to define routing routes and security policies.

Why use API gateway in microservices architecture?

The microservices architecture has seen a significant increase in popularity in recent years, primarily due to increased scalability, flexibility, and performance.

Since microservices-based applications comprise a number of different services, we often need a common interface or gateway to call these services so that we can define and manage all concerns in one place, instead of replicating them across all sites. later services.

This is precisely where an API gateway comes in, providing a single entry point for directing traffic to various microservices and a central place to implement security, orchestration, monitoring, etc.

Without a gateway, it would have to be deployed to each of the services and thus maintaining it would be a daunting task.

In short, we can leverage an API gateway to centralize, manage, and monitor an application’s non-functional requirements, orchestrate cross-functional microservices, and reduce round-trips. By consistently handling requests, an API gateway can help reduce latency and improve security.

Benefits of an API Gateway in microservices architecture

Improves isolation, by avoiding direct access to internal problems.

  • Allows you to create a single interface to call multiple services.
  • Allows you to add more services or change the limits without affecting external clients.

Enhanced security by providing a layer of security.

  • Improves security by providing an additional security layer, since microservices do not need to be exposed directly.
  • It provides a security layer that can help prevent attacks like SQL injection, Denial of Service (DoS), etc.
  • It provides an end-to-end encryption mechanism to protect the information that is transmitted between the services and the API Gateway.
  • It allows limiting incoming and outgoing traffic to prevent denial of service attacks.

Place for monitoring.

  • Since an API Gateway is a single component through which all requests and responses flow, it’s a great place to collect metrics.
  • Ocelot provides a variety of metrics and logs to help developers monitor and troubleshoot.

Eliminates code duplication and therefore reduces implementation effort.

  • It allows developers to centralize security, authentication, routing, and compliance management in one place, saving time and effort in application development and maintenance.

Improve latency.

  • It helps reduce latency by splitting a request into multiple requests if necessary and then routing them to the appropriate service.
  • By providing a single entry point for all requests, Ocelot reduces the number of round trips required to get a response.
  • If a user of a particular service needs data from multiple services, you need to authenticate the user only once, which reduces latency and makes your authentication mechanism consistent across your application.

Let’s go to code👇

Now that we know what an API Gateway is. If your coffee☕ is ready let’s get started

Microservices architecture example and API Gateway with Ocelot

In the use case, we will create 3 microservices and an API gateway with Ocelot, which will act as an intermediary between the apps and the services, allowing clients to access the services.

Technologies

How to install and configure Ocelot in a .NET project

1. We create a blank solution with the name “API-Gateway-Ocelot”

2. We will start by creating 2 ASP.NET Core Web API type projects, which will be our example microservices. “Service A” to get messages and “Service B” to get users.

We are not going to go into detail about how to create these microservices, because that is not the end of this tutorial. The code of the microservices used in this example can be seen and downloaded from the repository, I leave the download link at the end of the article.

Now, we will create a project called “OCELOT API Gateway” of type ASP.NET Core Web API, this project will be our API gateway. The entry point for directing traffic to other services.

4. Install the “Ocelot” NuGet package

Install-Package Ocelot

5. Initial Ocelot setup, the main items to consider are AddOcelot() that you add Ocelot services and UseOcelot().Wait() that you configure all the Ocelot middleware.

using Ocelot.DependencyInjection;
using Ocelot.Middleware;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOcelot();

var app = builder.Build();

app.UseOcelot().Wait();

app.Run();

6. We create “ocelot.json”, which is the configuration file in which we specify the routes and routing rules for the API gateway. In this file we can define several rules to control how the traffic is redirected towards the microservices.

7. We add the JSON configuration file that we have created with AddJsonFile().

using Ocelot.DependencyInjection;
using Ocelot.Middleware;

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);

builder.Services.AddOcelot();

var app = builder.Build();

app.UseOcelot().Wait();

app.Run();

8. Finally, in ocelot.json file properties we change to “copy always”, so that it is updated when compiling.

How to expose services through the API Gateway using Ocelot

Once our microservices and gateway are created, we go to the configurations to expose services through the gateway API.

1. We edit the configuration file “ocelot.json” and add:

  • Routes: Ocelot’s main functionality is to receive incoming http requests and forward them to a downstream service. Ocelot describes the routing from one request to another as a route. To configure a route, we must add an array called Routes:
    • DownstreamPathTemplateDownstreamScheme and DownstreamHostAndPorts define the URL to which a request will be forwarded. DownstreamHostAndPorts is an array because Ocelot allows you to add more than one entry and then select a load balancer.
    • UpstreamPathTemplate is the URL that Ocelot will use to identify which DownstreamPathTemplate to use for a given request. UpstreamHttpMethod it is used so that Ocelot can distinguish between requests with different HTTP verbs to the same URL (if the array is empty it accepts all verbs).
  • GlobalConfiguration: Ocelot needs to know what BaseUrl it is running under to find and replace headers and for certain management settings.
{
  "Routes": [
    {
      "DownstreamPathTemplate": "/posts",
      "DownstreamScheme": "https",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 7019
        }
      ],
      "UpstreamPathTemplate": "/api/posts",
      "UpstreamHttpMethod": [ "Get" ]
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "https://localhost:5000"
  }
}

2. We configure “launchSettings.json”, which contains the settings that control how the web application starts on the development computer: 

{
  "$schema": "https://json.schemastore.org/launchsettings.json",
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:44343",
      "sslPort": 5000
    }
  },
  "profiles": {
    "OCELOT-API-Gateway": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "launchUrl": "todos",
      "applicationUrl": "https://localhost:5000;",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

3. Finally, to start multiple projects at once, we go to the solution properties, under “Startup Project”, select “Multiple Startup Projects” and select “Start” on all projects.

And at this moment, if we start applications and go to the following url: https://localhost:5000/todos/1 We can now access our API Gateway!?

You can also capture all types of routes using: {everything}

{
  "DownstreamPathTemplate": "/{everything}",
  "DownstreamScheme": "https",
  "DownstreamHostAndPorts": [
    {
      "Host": "localhost",
      "Port": 7019
    }
  ],
  "UpstreamPathTemplate": "/api/{everything}",
  "UpstreamHttpMethod": [ "Get" ]
}

This will forward any route + query string combination to the downstream service after the /api route. To test the following urls: https://localhost:5000/api/postshttps://localhost:5000/api/posts/1

How to implement some Ocelot features

  1. Request aggregation in API gateways with Ocelot
  2. Authentication to API gateways with Ocelot
  3. Authorization in API gateways with Ocelot
  4. Rate Limiting on API Gateways with Ocelot
  5. Delegating Handlers in API Gateways with Ocelot
  6. API Gateway Caching with Ocelot

Request aggregation in API gateways with Ocelot

Ocelot allows us to specify aggregate routes that make up several normal routes and map their responses into a single object. In this way a client making multiple requests to a server could now only be one.

1. We add to the Ocelot configuration file an array called Aggregates.

The object within the RouteKeys list describes an aggregation of two services, identified by the “posts” and “users” route keys. The UpstreamPathTemplate property sets the template of the route to be used for the main service, in this case it is “/”.

The “Aggregator” property defines the name of the class that implements the aggregation logic, in this case PostsUserAggregator.

{
  "Routes": [
    {
      ...
      "Key": "posts"
    },
    {
      ...
      "Key": "users"
    }
  ],
  "GlobalConfiguration": {
    ...
  },
  "Aggregates": [
    {
      "RouteKeys": [
        "posts",
        "users"
      ],
      "UpstreamPathTemplate": "/",
      "Aggregator": "PostsUserAggregator"
    }
  ]
}

2. We create a class “PostsUserAggregator” that inherits from IDefinedAggregator. This class will be responsible for taking the responses from the “posts” and “users” services and combining them into a single response. Ocelot will look for this aggregator when trying to access this path.

public class PostsUserAggregator : IDefinedAggregator
{
    public async Task<DownstreamResponse> Aggregate(List<HttpContext> responses)
    {
        var posts = await responses[0].Items.DownstreamResponse().Content.ReadFromJsonAsync<List<PostDto>>();
        var users = await responses[1].Items.DownstreamResponse().Content.ReadFromJsonAsync<List<UserDto>>();

        posts?.ForEach(post =>
        {
            post.User = users.FirstOrDefault(a => a.Id == post.UserId);
        });

        var jsonString = JsonConvert.SerializeObject(posts, Formatting.Indented, new JsonConverter[] { new StringEnumConverter() });

        var stringContent = new StringContent(jsonString)
        {
            Headers = { ContentType = new MediaTypeHeaderValue("application/json") }
        };

        return new DownstreamResponse(stringContent, HttpStatusCode.OK, new List<KeyValuePair<string, IEnumerable<string>>>(), "OK");
    }
}

3. To make the aggregator available, we need to add a singleton instance of the PostsUserAggregator aggregator type:

builder.Services
    .AddOcelot()
    .AddSingletonDefinedAggregator<PostsUserAggregator>();

4. Now we can access this aggregate and get posts and users in a single call from two different microservices, using url https://localhost:5000/api/posts-user

[
{
    "Id": 1,
    "Title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "Body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam...",
    "UserId": 1,
    "User": {
        "Id": 1,
        "Name": "Leanne Graham",
        "Username": "Bret",
        "Email": "Sincere@april.biz",
        "Address": {
            "Street": "Kulas Light",
            "Suite": "Apt. 556",
            "City": "Gwenborough",
            "Zipcode": "92998-3874",
            "Geo": {
                "Lat": "-37.3159",
                "Lng": "81.1496"
            }
        },
        "Phone": "1-770-736-8031 x56442",
        "Website": "hildegard.org",
        "Company": {
            "Name": "Romaguera-Crona",
            "CatchPhrase": "Multi-layered client-server neural-net",
            "Bs": "harness real-time e-markets"
        }
    }
},
...
]

Note: The aggregation only supports the GET HTTP verb.

Authentication to API gateways with Ocelot

Ocelot allows us to secure by authenticating at the route level within the context of the API gateway.

1. First of all, we are going to create a new service called “ServicioAuthentication”, which we will need to obtain the token, which we will use to connect to the rest of the microservices. We are not going to go into detail how to implement the logic that returns the token to us. A token is provided to a client after it has been authenticated, and this token can be sent by the client on all subsequent requests to authenticate and authorize the requested action. I leave download link at the end of the article.

2. Now, we are going to configure authentication on our API gateway, we need to install the NuGet package “AspNetCore.Authentication.JwtBearer”

Install-Package Microsoft.AspNetCore.Authentication.JwtBearer

3. To authenticate a route, we must first register the authentication services in “program.cs” our API gateway:

builder.Services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,

            ValidIssuer = builder.Configuration["JwtSettings:Issuer"],
            ValidAudience = builder.Configuration["JwtSettings:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JwtSettings:Key"]))
        };
    });

    ...

    app.UseAuthorization();

4. Then we add IssuerAudience and Key to the configuration file “appsettings.json” of our API gateway (this data can be obtained from “appsettings.json” the Authentication Service):

{
  ...
  "AllowedHosts": "*",
  "JwtSettings": {
    "Issuer": "arbems.com",
    "Audience": "Public",
    "Key": "G3VF4C6KFV43JH6GKCDFGJH45V36JHGV3H4C6F3GJC63HG45GH6V345GHHJ4623FJL3HCVMO1P23PZ07W8"
  }
}

5. El siguiente código configura una ruta específica en la puerta de enlace API para requerir autenticación de token JWT. Se especifica que el proveedor de autenticación a usar es “Bearer” y se especifica que no hay ámbitos (scopes) permitidos específicos para acceder a esta ruta. Esto significa que todo token válido emitido por el emisor y destinatario especificados será permitido para acceder a esta ruta, sin importar los ámbitos incluidos en el token.

"Routes": [
    {
      ...
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "Bearer",
        "AllowedScopes": []
      },
      ...
    }

If we access the route https://localhost:5000/api/posts, which is the one that we have secured with authentication, without adding a token we get 401 Unauthorized

6. We need the token to be authorized to access the protected resources until the token is revoked or expires, we obtain it by calling the microservice “ServicioAutenticacion”, with the following links and parameters:

We copy the obtained token and try again to access the route from Postman , which is an application that allows us to test APIs through a graphical user interface:

Now we are authenticated, we can access the route and see the results!✌️

Authorization in API gateways with Ocelot

Ocelot supports claims-based authorization that runs after authentication. This means that we can secure a route through authorization.

1. We add a property of a route called RouteClaimsRequirement to our route configuration. This property specifies a requirement to access this particular route. In this case, it is stated that the user must have a UserType claim with a value of “authorized” in order to access this route.

This means that only users who meet the requirement (ie, who have a “UserType” assertion with a value of “authorized” in their authorization token) will be allowed to access the specified path. If a user does not meet the requirement, they will receive an authorization error and will not be able to access the route, receiving a 403 Forbidden response.

...
"RouteClaimsRequirement": {
    "UserType": "authorized"
}
...

Getting the token with this link: https://localhost:5000/api/token?username=user&userType=unauthorized you will not be authorized. With the following link yes, https://localhost:5000/api/token?username=admin&userType=authorized

Now we have protected the routes according to user authorizations?‍♂️

Rate Limiting on API Gateways with Ocelot

Ocelot supports rate limiting of upstream requests so that your downstream services are not overloaded.

This configuration defines the rate limiting implementation in Ocelot. The property EnableRateLimiting turns rate limiting on or off. The property ClientWhitelist is a list of allowed clients that are exempt from rate limitation. The property Period specifies the time interval in which the rate limitation is applied. The property PeriodTimespan is the number of time slots in which the rate limitation is applied. The property Limit specifies the number of requests allowed in the specified time interval.

In this case, the rate cap is applied to 1 request per second for all clients except those on the whitelist.

"RateLimitOptions": {
    "ClientWhitelist": [],
    "EnableRateLimiting": true,
    "Period": "1s",
    "PeriodTimespan": 1,
    "Limit": 1
}

We can also configure the following in the GlobalConfiguration part of “ocelot.json”.

"RateLimitOptions": {
  "DisableRateLimitHeaders": false,
  "QuotaExceededMessage": "Customize Tips!",
  "HttpStatusCode": 999,
  "ClientIdHeader" : "Test"
}

The options included in this block allow you to customize the behavior of the gateway in case the set rate limit is exceeded.

DisableRateLimitHeaders allows you to disable the headers related to the rate limit.

QuotaExceededMessage allows you to customize the message displayed when the rate limit is exceeded.

HttpStatusCode allows you to set a custom HTTP status code for cases where the rate limit is exceeded.

ClientIdHeader allows you to set the header that is used as the client identifier for the rate limit.

Delegating Handlers in API Gateways with Ocelot

Ocelot allows the user to add delegation handlers to the HttpClient transport. It can be used to intercept any request and perform validation, for example, or even put some log to monitor. As the name implies, delegation handlers are basically message handlers. Requests and returns go through them.

1. First, let’s create a class, called “BlackListHandler” which will inherit the class DelegatingHandler (from the System.Net.Http library) and implement the SendAsync() method.

public class ApiKeyHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return base.SendAsync(request, cancellationToken);
    }
}

2. Next we need to add the controllers to the Ocelot container:

builder.Services
    .AddOcelot()
    .AddDelegatingHandler<ApiKeyHandler>(true);

3. Now we need to configure Ocelot to consider the DelegatingHandler on all requests. To do this, we’ll add a new property called DelegatingHandlers “ocelot.json”, where we’ll pass the name of the class:

{
  ...
  "DelegatingHandlers": [
    "ApiKeyHandler"
  ]
}

4. Now we are going to add an example validation. In this case, validate that we receive a specific Header, if we do not receive it, we will throw an exception:

public class ApiKeyHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (!ValidateKey(request))
        {
            var response = new HttpResponseMessage(HttpStatusCode.BadRequest);
            return response;
        }
        return await base.SendAsync(request, cancellationToken);
    }

    private bool ValidateKey(HttpRequestMessage request)
    {
        if (request.Headers.TryGetValues("ApiKey", out IEnumerable<string> myHeaderList))
        {
            var myHeader = myHeaderList.First();

            //do something with the header value
            if (!string.IsNullOrEmpty(myHeader) && myHeader == "123456")
                return true;
        }

        return false;
    }
}

5. We try from Postman and add ApiKey with value 123456. If we don’t add header we will get a 400 Bad Request error

API Gateway Caching with Ocelot

Ocelot supports caching provided by CacheManager , we will show how to add CacheManager to Ocelot so that it can do output caching.

1. First, we install the CacheManager nuget package

Install-Package Ocelot.Cache.CacheManager

2. Second, we configure the service in “program.cs”

...
builder.Services.AddOcelot()
    .AddCacheManager(x =>
    {
        x.WithDictionaryHandle();
    })
...

3. Finally, to use caching in a route we add configuration:

{
  ...
  "FileCacheOptions": {
    "TtlSeconds": 15,
    "Region": "somename"
  }
  ...
}

With TtlSeconds you set the lifetime in seconds of the elements in the file cache. With Region the name of the region for the file cache is specified. By using the file cache, Ocelot can reduce the number of requests to the back-end and improve application performance.

To test we access the route https://localhost:5000/api/users the results will be stored in the cache and will expire after 15 seconds.

Conclusion

In conclusion, Ocelot is an excellent option to implement an API Gateway in .NET applications. It offers a lot of useful features such as routing, request and response transformation, authentication and authorization, and many more. Plus, it’s easy to set up and customize, making it ideal for projects of all sizes. If you are looking for a solution to implement an API Gateway in your .NET application, you should definitely consider using Ocelot.

That’s all for now, I’ll keep updating and adding content in this post. I hope you found it interesting😉

Download

The source code for this article can be found on  GitHub