• About Blog

    What's Blog?

    A blog is a discussion or informational website published on the World Wide Web consisting of discrete, often informal diary-style text entries or posts.

  • About Cauvery Calling

    Cauvery Calling. Action Now!

    Cauvery Calling is a first of its kind campaign, setting the standard for how India’s rivers – the country’s lifelines – can be revitalized.

  • About Quinbay Publications

    Quinbay Publication

    We follow our passion for digital innovation. Our high performing team comprising of talented and committed engineers are building the future of business tech.

Monday, March 8, 2021

JSON Web Token (JWT)

JSON Web Token Image

What is JWT ? How it works ? How can it keep our applications secure?

JSON Web Tokens has become the favourite choice among modern developers when implementing user authentication. Let’s understand what JWT is and how it works, specifically in the context of securing web applications.

There is an open industry standard specification called RFC 7519 that outlines how a JWT should be structured and how to use it for exchanging the information between parties as JSON objects.

Authentication is basically what happens when users sign-in. We check the user’s identity based on credentials like username/password.

Authorization, on the other hand, checks if the above-validated user is able to access specified modules or not

There are multiple ways that web applications can manage sessions and two of the popular ways is by using the tokens.

Session Tokens

In this mechanism, the server will create a session for the user after the user is successfully authenticated. The session will have an unique identifier, which is stored as a cookie on the users browser. While the user stays logged in, the cookie would be sent along with every subsequent request.

The server parses the cookie and then compares the session id against the session information stored in the memory or data store to verify the user’s identity and provides the user context to the application.

The biggest problem with this approach is, it assumes that, there is always just one monolithic server web application. That used to be the case typically in the past. But that’s no longer the case these days as we live in micro services world.

There would be multiple servers that share the load that sit behind a load balancer. When a request comes in, the load balancer decides which server to route the request. The user could have had their login request routed to one server, but the next request goes through the load balancer and may land on a different server. Now this new server has no idea about the previous interaction.

Being a techie, we may find a solution for it. Let’s say, we introduce a shared cache that all these servers persist and look up user session information which will solve the problem.

JSON Web Tokens

In this mechanism, the authentication server will authenticate the user and generate a JWT which will have all the required information. It will be sent back to the client for later usage. This is more scalable solution as JWT is stateless, which means, the user state is never stored on the server but the state is stored inside the token itself.

If the user is making a subsequent request to the application, the JWT needs to be added with the request. The application server will be configured to be able to check whether the incoming JWT is exactly what was created by the authentication server.

Let’s look at the format of the JWT to understand it better. A JSON Web Token consists of 3 sections separated by periods.

JWT Representation Image

Header

The header section typically contains 2 details — the type of token (JWT in this case) and the hashing algorithm used by the token such as RSA, HMAC, or SHA256. The default algorithm used is HS256.

Payload

The payload section contains actual data pertaining to a user is what we call as claims. The claims can be of 3 types:

Reserved Claims

These are some pre-defined claims which are not mandatory but recommended to use it as a best practise. These claims help the application judge the authenticity of the token. Listing few of them for sample are iss (issuer), sub (subject), exp (expiration time) etc.

Public Claims

These can be defined based on the requirements by those using JWTs. As it’s a public claims, to avoid issues they should be defined in the IANA JSON Web Token Registry.

Private Claims

These are the custom claims created to share information between parties that agree on using them. Listing few of them as sample are employment type, department name etc.

If anyone is interested to read more about claims, you can read it over here.

Signature

The signature is the most important part of a JSON Web Token. It is calculated by encoding the header and payload using Base64URL Encoding and concatenating them with a period as separator, which is then run through the cryptographic algorithm. Please remember when the header or payload changes, the signature has to be calculated again.

// Signature Algorithm
jwtData = base64urlEncode(header) + "." + base64urlEncode(payload)
signature = HMAC(jwtData, secret_salt)

// Token Generation
token = encodeBase64Url(header) + "." + encodeBase64Url(payload) + "." + encodeBase64Url(signature)

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiaXNzIjoiQmhhcmdhdiBJbmMuIiwiZXhwIjoxNjEzOTM4Mzg3LCJpYXQiOjE2MTM5MjAzODd9.XblnkCqOUdtjLIg2pJcN_7gXUc7nSHIuXnBwin8hSeQ


What’s next ?

Shhh! Let me tell you a secret. Go to jwo.io website, copy the above JWT and paste it in the encoded section of the online debugger. Voila, you could see all the data stored in the token. Now you will have a question on your mind, what the heck, how is this secure? 🤔

Please note that, JWT’s are encoded but not encrypted. It is a mechanism by which you can verify that the data is not tampered and has come from the trusted source.

The two open industry standards that describe the security features of JWT are RFC 7515 for JSON Web Signature and RFC 7516 for JSON Web Encryption.

JSON Web Signature

The purpose of a signature is to allow one or more parties to establish the authenticity of the JWT. Now if you remember the signature is basically the encoded header and payload concatenated with a period and then run through a hashing algorithm with a secret key.

The signature attached at the end helps us to determine if the JWT has been tampered with because for any change in the data the signature will change. A signature, however does not prevent third parties from reading the contents of the JWT.

JSON Web Encryption

JWS provides us to establish the authenticity of the JWT contents, where as JWE provides a way to keep the contents of the JWT unreadable to third parties.

An encrypted JWT, can use two cryptographic schemes — a shared secret scheme or a public/private-key scheme.

Conclusion

JWT is a modern and robust solution to authenticate and authorise users and sharing sensitive information while not maintaining state. A JWT is made of three parts — header, payload and signature. Sending JWTs in cookies instead of in the header, shortening their expiration time and using refresh tokens to issue new access tokens are some of the security measures we can take to guarantee the security of our application, its users and their data.

Monday, February 22, 2021

S.O.L.I.D — Design Principles

 

S.O.L.I.D — Design Principles Image

Software Design Principles

In today’s world, customer requirements keep changing at an unprecedented pace. It becomes essential for the technical teams to accommodate the new requirements and deliver those very quickly. To develop and deliver faster, it’s necessary to reduce software development and testing time.

At the same time, new technologies are introduced every few months. It’s common to experiment with more optimal and efficient technologies by replacing the existing ones. Thus, it’s important to write the code that is flexible and loosely coupled to introduce any changes.

Well written code is easy to grasp as new developer doesn’t have to spend more time reading the code. A well maintained software thus enhances developer’s and the team’s productivity. In addition, high test coverage increases the confidence to deploy a new change to the production.


Where do SOLID principles come from?

SOLID principles came from an essay written in 2000 by Robert Martin, known as Uncle Bob, where he discussed that a successful application will change and, without good design, can become rigid, fragile, immobile and viscous.

  • Rigid — Things are very fixed. You can’t move or change things without affecting other things, but it’s clear what will break if you make a change.
  • Fragile — Easy to move and change things but not obvious what else might break as a result.
  • Immobile — Code works fine but you can’t re-use code without duplicating or replicating it.
  • Viscous — Everything falls apart when you make a change, you quickly push it back together and get your change working. The same thing happens when somebody else comes along to make a change.

The principles that Robert Martin talks about to avoid the four design anti-patterns above have evolved to be known as the SOLID principles. Although he didn’t invent the principles, he pulled together some good coding practices that already existed around a central theme of managing dependencies and put forward a good argument for using those practices together.

In object-oriented world, S.O.L.I.D is a mnemonic acronym for five design principles intended to make software designs more understandable, flexible, and maintainable. Let’s go through all five principles.

Single Responsibility Principle

One of the simplest principles to understand. It states that, a class should only have one responsibility. Furthermore, it should only have one reason to change.

Open/Closed Principle

Simply put, classes should be open for extension, but closed for modification. In doing so, we stop ourselves from modifying existing code and causing potential new bugs.

Liskov Substitution Principle

The principle states that, objects of the same superclass should be able to substitute each other without breaking an existing code.

Interface Segregation Principle

According to this principle, the larger interfaces should be split into smaller ones. By doing so, we can ensure that implementing classes only need to be concerned about the methods that are of interest to them.

Dependency Inversion Principle

The principle of Dependency Inversion refers to the decoupling of software modules. This way, instead of high-level modules depending on low-level modules, both will depend on abstractions.

Conclusion

The above five principles form a foundation for the best practices followed in Software Engineering. Practicing the above principles in day to day work helps improve the readability, modularity, extensibility and testability of the software.

One thing you can guarantee with any application, if it’s successful and used extensively, it will change over the time. As it changes, the complexity factor gradually increases until you hit a tipping point where it becomes more difficult and takes longer time to ship new features on top of the poorly written code that was quickly shipped once.

I’ll leave you with following questions which I’ve found useful to ask myself while writing code:
  • Is the class DRY(Don’t Repeat Yourself)?
  • Does everything in a class change at the same rate?
  • Have I abstracted out something that is likely to change?
  • Have I abstracted out something that is not used in all classes that inherit it?

Wednesday, February 10, 2021

Vue JS — Best Practices

 Vue JS Logo

VueJS — The Progressive JavaScript Framework

Vue (pronounced /vjuː/, like view) is a progressive framework for building user interfaces. Unlike other monolithic frameworks, Vue is designed from the ground up to be incrementally adoptable. The core library is focused on the view layer only, and is easy to pick up and integrate with other libraries or existing projects. On the other hand, Vue is also perfectly capable of powering sophisticated Single-Page Applications when used in combination with modern tooling and supporting libraries.

If you are an experienced frontend developer and want to know how Vue compares to other libraries/frameworks, check out the Comparison with other Frameworks.

Lifecycle Hooks

Every Vue instance goes through a series of initialisation steps. When it is created from setting up data observation to compiling the template, to mounting the instance to the DOM, and finally to updating the DOM during data changes. This process is known as the lifecycle of a Vue instance and they have some functions run inside them by default as they go through this process of creating and updating the DOM. It is inside them that Vue components are created and manipulated, these functions are called lifecycle hooks.

There are eight lifecycle methods:

  1. Before Create
  2. Created
  3. Before Mount
  4. Mounted
  5. Before Update
  6. Updated
  7. Before Destroy
  8. Destroyed

If interested, take a look at  the Lifecycle Diagram of Vue JS. Refer to the official page which has complete details about each lifecycle hooks.

Be Aware of Camel vs Kebab Case

JavaScript are case-sensitive aka Counter is not equal to counter. As HTML, in general is case-insensitive, especially the HTML attributes. As VueJS spans both of these worlds, it’s better to understand this clearly.

camelCase

Start with lower-case and combine subsequent words by capitalise them. Example: amInCamelCase.

kebab-case

Write everything in lower-case and separate words by hyphens. Example: this-is-a-kebab-case.

To help you out, VueJS translates camelCase prop names into kebab-cased equivalent. But whenever things happen automatically, you need to be aware of it.

Vue.component('my-component', {
  props: ['myPropertyMessage'], // camelCase in JavaScript
  template: '<h1>{{myPropertyMessage}}</h1>'
})

The above JS code will translate to something like below

<!-- kebab-case in HTML -->
<my-component my-property-message="Hello World!"></my-component>

Hence declare props with camelCase and use Kebab Case in Templates.

Always use kebab-case for event names

When emitting/listening to custom events, we should always use kebab-case. Why? Because the events will be transformed automatically into lowercase anyway. We wont be listening to an event in camelCase or PascalCase, therefore, makes more sense to declare the event the same way we are going to listen to it: in kebab-case.

// Emitting
this.$emit('my-event') // instead of myEvent

// Listening
v-on:my-event

Always use :key in v-for loops

Is a common best practice to always add a :key to your template loops. A v-for without a :key can lead to hard to find errors, especially with certain kind of components or objects.

Don’t use v-if With v-for Elements

It’s a clear advice from the style guide from vuejs.org

<div v-for='plan in subscriptionPlans' v-if='plan.type == 1'></div>

The reason is that VueJS assigns a higher priority of the v-for than the v-if. This leads to a performance issue as the loop would run and filter inside instead of filter first.

The best practice here is to reduce the size to iterate over before the iteration by using a computed property:

computed: {
  onSaleSubscriptions: function() {
    return this.subscriptionPlans.filter(function (plan) {
       return(plan.type == 1)
    })
  }
}

Use $_ for mixins properties

Mixins are a great way to get repeated code into one single block and import it as many times as you want, but this can lead to several issues. In this point we will address the issue of overlapping properties.

When we import a mixin into our Component we are merging the mixin code with our component code. Now what happens with property that have the same name? Component will always have the upper hand as their properties have higher priority.

What if I want my mixin to have more priority? You can’t assign a priority but you can avoid properties from overlapping or even from overwriting by choosing a correct naming convention.

In order to differentiate mixin properties from Component properties we use $_. Why these symbols? Well, several reasons:
  • Convention from VueJs style guide
  • _ is reserved for Vue’s private properties
  • $ is reserved for Vue’s ecosystem

var authMixin = {
   //...
   methods: {
      $_authMixinGetUserInfo(){
         //...
      }
   }
}

Clear event listeners on component destroy with $off

When listening to events with $on, we should always remember to remove that listener with $off on destroyed(). This prevents us from having memory leaks.

const EmployeeComponent = { 
  mounted() {
    console.log("Employee component mounted");
    this.notify.$on("manager", this.startedListening);
  },
  beforeDestroy() {
    console.log("Employee component before destroy");
    // Remove all listening events.
    this.notify.$off("manager", this.stoppedListening);
  },
  methods: {
    startedListening() {
      // callback
    },
    stoppedListening() {
      // callback
    },
  },
};

Sources:


Thursday, January 21, 2021

Rate Limiter Implementation — Sliding Log Algorithm

Sliding Log Image

API Rate Limiting

Rate limiting is a strategy to limit the access to APIs. It restricts the number of API calls that a client can make within any given timeframe. This helps to defend the API against abuse, both unintentional and malicious scripts.

Rate limits are often applied to an API by tracking the IP address, API keys or access tokens, etc. As an API developers, we can choose to respond in several different ways when a client reaches the limit.

  • Queueing the request until the remaining time period has elapsed.
  • Allowing the request immediately but charging extra for this request.
  • Most common one is rejecting the request (HTTP 429 Too Many Requests)

Sliding Log Algorithm

Sliding Log rate limiting involves tracking a time stamped log for each consumer request. These logs are usually stored in a hash set or table that is sorted by time. Logs with timestamps beyond a threshold are discarded. When a new request comes in, we calculate the sum of logs to determine the request rate. If the request would exceed the threshold rate, then it is held.

The advantage of this algorithm is that it does not suffer from the boundary conditions of fixed windows. The rate limit will be enforced precisely and because the sliding log is tracked for each consumer, you don’t have the rush effect that challenges fixed windows. However, it can be very expensive to store an unlimited number of logs for every request. It’s also expensive to compute because each request requires calculating a summation over the consumers prior requests, potentially across a cluster of servers. As a result, it does not scale well to handle large bursts of traffic or denial of service attacks.

Please refer to the Understanding Rate Limiting Algorithms blog where the Sliding Log and other algorithms have been explained in detail.

Building a Springboot Application with API Rate Limiter

Create a new spring boot application from Spring Initializr with dependency on spring web module.

Unzip the downloaded project and import to your IDE. We are going to implement a simple calculator REST APIs that can do operations like add and subtract.

@RestController
@RequestMapping(value = "/api/calculator")
public class CalculatorController {
    @GetMapping(value = "/add")
    public ResponseEntity<Calculator> add(@RequestParam int left, @RequestParam int right) {
        return ResponseEntity.ok(Calculator.builder()
                .operation("add").answer(left + right).build());
    }
    @GetMapping(value = "/subtract")
    public ResponseEntity<Calculator> subtract(@RequestParam int left, @RequestParam int right) {
        return ResponseEntity.ok(Calculator.builder()
                .operation("subtract").answer(left - right).build());
    }
}

Let’s ensure that our above APIs are up and running as expected. You can use the cURL or PostMan to make an API call.

curl -X GET -H "Content-Type: application/json" 'http://localhost:9090/api/calculator/add?left=20&right=30'{"operation":"add","answer":50}

Now that we have APIs ready to consume, next let’s introduce some subscription plans with rate limits. Let’s assume that we have the following subscription plans for our clients:
Free Subscription allows 2 requests per 60 seconds.
Basic Subscription allows 10 requests per 60 seconds.
Professional Subscription allows 20 requests per 60 seconds.

Each API client gets a unique API key that they must send along with each request. This would help us identify the client and subscription plan linked.

public enum SubscriptionPlan {

    SUBSCRIPTION_FREE(2, 60),
    SUBSCRIPTION_BASIC(10, 60),
    SUBSCRIPTION_PROFESSIONAL(20, 60);

    private final int requestLimit;
    private final int windowTime;

    SubscriptionPlan(int requestLimit, int windowTime) {
        this.requestLimit = requestLimit;
        this.windowTime = windowTime;
    }

    public int getRequestLimit() {
        return this.requestLimit;
    }

    public int getWindowTime() {
        return this.windowTime;
    }

}

Next we create a subscription service which will store the references for each of the API client in a memory.

@Service
public class SubscriptionService {

    private final Map<String, UserRequestData>
            subscriptionCacheMap = new ConcurrentHashMap<>();

    public UserRequestData resolveSubscribedUserData(String subscriptionKey) {
        return subscriptionCacheMap.computeIfAbsent(
                subscriptionKey, this::resolveUser);
    }

    private UserRequestData resolveUser(String subscriptionKey) {
        if (subscriptionCacheMap.containsKey(subscriptionKey)) {
            return subscriptionCacheMap.get(subscriptionKey);
        }
        return buildUserLog(
                resolveSubscriptionPlanByKey(subscriptionKey));
    }

    private UserRequestData buildUserLog(SubscriptionPlan subscriptionPlan) {
        return new UserRequestData(
                subscriptionPlan.getRequestLimit(), 
                subscriptionPlan.getWindowTime());
    }

    private SubscriptionPlan resolveSubscriptionPlanByKey(String subscriptionKey) {
        if (subscriptionKey.startsWith("PS1129-")) {
            return SubscriptionPlan.SUBSCRIPTION_PROFESSIONAL;
        } else if (subscriptionKey.startsWith("BS1129-")) {
            return SubscriptionPlan.SUBSCRIPTION_BASIC;
        }

        return SubscriptionPlan.SUBSCRIPTION_FREE;
    }

}

Let’s understand the implementation. The API client sends an API key with the X-Subscription-Key request header. We use the SubscriptionService to get the user reference for the API key and check whether the request is allowed or not with the help of methods.

In order to enhance the client experience of the API, we will add the following additional response headers to send information about the rate limit.
  • X-Rate-Limit-Remaining — number of tokens remaining in the current time window.
  • X-Rate-Limit-Retry-After-Seconds — remaining time in seconds until the bucket is refilled with new tokens.
We can call UserRequestData methods getRequestWaitTime and getRemainingRequests, to get the count of the remaining requests and the time remaining until the next sliding log respectively. The implementation provided in this class is self explanatory and easy to understand the same.

public class UserRequestData {

    private int requestLimit;
    private int windowTimeInSec;
    private Queue<Long> requestTimeStamps;

    public UserRequestData(
            int requestLimit, int windowTimeInSec) {
        this.requestLimit = requestLimit;
        this.windowTimeInSec = windowTimeInSec;
        this.requestTimeStamps =
                new ConcurrentLinkedDeque<Long>();
    }

    public int getRemainingRequests() {
        return requestLimit - requestTimeStamps.size();
    }

    public int getRequestWaitTime() {
        long currentTimeStamp =
                System.currentTimeMillis() / 1000;
        int initialElapsedTime =
                (int) (currentTimeStamp - requestTimeStamps.peek());
        return initialElapsedTime > windowTimeInSec
                ? 0 : windowTimeInSec - initialElapsedTime;
    }

    public boolean isServiceCallAllowed() {
        long currentTimeStamp =
                System.currentTimeMillis() / 1000;
        evictOlderRequestTimeStamps(currentTimeStamp);

        if (requestTimeStamps.size() >= this.requestLimit) {
            return false;
        }

        requestTimeStamps.add(currentTimeStamp);
        return true;
    }

    public void evictOlderRequestTimeStamps(long currentTimeStamp) {
        while (requestTimeStamps.size() != 0 &&
                (currentTimeStamp - requestTimeStamps.peek() 
                        > windowTimeInSec)) {
            requestTimeStamps.remove();
        }
    }

}

Here is the implementation of the Interceptor to validate the request with rate limiter to see whether we accept or reject the request.

@Component
public class RateLimiterInterceptor implements HandlerInterceptor {

    private static final String
            HEADER_SUBSCRIPTION_KEY = "X-Subscription-Key";
    private static final String
            HEADER_LIMIT_REMAINING = "X-Rate-Limit-Remaining";
    private static final String
            HEADER_RETRY_AFTER = "X-Rate-Limit-Retry-After-Seconds";
    private static final String
            SUBSCRIPTION_QUOTA_EXHAUSTED =
            "You've exhausted your API Request Quota. " +
            "Please upgrade your subscription plan.";

    @Autowired
    private SubscriptionService subscriptionService;

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        String subscriptionKey =
                request.getHeader(HEADER_SUBSCRIPTION_KEY);
        if (StringUtils.isEmpty(subscriptionKey)) {
            response.sendError(HttpStatus.BAD_REQUEST.value(),
                    "Missing Request Header: " +
                       HEADER_SUBSCRIPTION_KEY);
            return false;
        }

        UserRequestData userRequestData = subscriptionService
                        .resolveSubscribedUserData(subscriptionKey);
        if (!userRequestData.isServiceCallAllowed()) {
            int waitTime = userRequestData.getRequestWaitTime();
            response.addHeader(HEADER_RETRY_AFTER,
                    String.valueOf(waitTime));

            response.setContentType(
                    MediaType.APPLICATION_JSON_VALUE);
            response.sendError(
                    HttpStatus.TOO_MANY_REQUESTS.value(),
                    SUBSCRIPTION_QUOTA_EXHAUSTED);
            return false;
        }

        response.addHeader(HEADER_LIMIT_REMAINING,
                String.valueOf(
                    userRequestData.getRemainingRequests()));
        return true;
    }
}

Finally, let’s add the interceptor to the InterceptorRegistry of Springboot so that the RateLimitInterceptor intercepts each request to our calculator API endpoints.

@SpringBootApplication
public class SlidingWindowApplication implements WebMvcConfigurer {

    @Autowired
    @Lazy
    private RateLimiterInterceptor interceptor;

    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptor)
                .addPathPatterns("/api/calculator/**");
    }

    public static void main(String[] args) {
        SpringApplication.run(SlidingWindowApplication.class, args);
    }

}

Let invoke calculator API to see the behaviour.

curl -X GET 'http://localhost:9090/api/calculator/add?left=20&right=30'
{"timestamp":"2021-01-03T12:56:20.047+0000","status":400,"error":"Bad Request","message":"Missing Request Header: X-Subscription-Key","path":"/api/calculator/add"}

The client has to send the API key within the http header otherwise the interceptor will not process the request. Let’s add the API key to the header and make the call.

curl -v -X GET -H "X-subscription-key:A1129-12" 'http://localhost:9090/api/calculator/subtract?left=20&right=30'
* Connected to localhost (::1) port 9090 (#0)
> GET /api/calculator/subtract?left=20&right=30 HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.64.1
> Accept: */*
> X-subscription-key:A1129-12
>
< HTTP/1.1 200
< X-Rate-Limit-Remaining: 1
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Sun, 03 Jan 2021 12:57:09 GMT
<
* Connection #0 to host localhost left intact
{"operation":"subtract","answer":-10}
* Closing connection 0

You can see the API key is added in the header, the API responds to our request and also it has added response header which shows how many rate is remaining for the API key.

Let’s make 2 more calls then we should see that we exhausted our rate for the free plan and returns 429 as response.

curl -v -X GET -H "X-subscription-key:A1129-12" 'http://localhost:9090/api/calculator/subtract?left=20&right=30'
* Connected to localhost (::1) port 9090 (#0)
> GET /api/calculator/subtract?left=20&right=30 HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.64.1
> Accept: */*
> X-subscription-key:A1129-12
>
< HTTP/1.1 429
< X-Rate-Limit-Retry-After-Seconds: 24
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Sun, 03 Jan 2021 12:58:58 GMT
<
* Connection #0 to host localhost left intact
{"timestamp":"2021-01-03T12:58:58.176+0000","status":429,"error":"Too Many Requests","message":"You've exhausted your API Request Quota. Please upgrade your subscription plan.","path":"/api/calculator/subtract"}
* Closing connection 0

It looks like we have successfully implemented the rate limiter using the Sliding Log algorithm. We can keep adding endpoints and the interceptor would apply the rate limit for each request.

As usual, the source code for the above spring boot implementation is available over on GitHub.

Monday, January 4, 2021

Rate Limiter Implementation — Token Bucket Algorithm

Token Bucket Image

API Rate Limiting

Rate limiting is a strategy to limit the access to APIs. It restricts the number of API calls that a client can make within any given timeframe. This helps to defend the API against abuse, both unintentional and malicious scripts.

Rate limits are often applied to an API by tracking the IP address, API keys or access tokens, etc. As an API developers, we can choose to respond in several different ways when a client reaches the limit.

  • Queueing the request until the remaining time period has elapsed.
  • Allowing the request immediately but charging extra for this request.
  • Most common one is rejecting the request (HTTP 429 Too Many Requests)

Token Bucket Algorithm

Assume that we have a bucket, the capacity is defined as the number of tokens that it can hold. Whenever a consumer wants to access an API endpoint, it must get a token from the bucket. Token is removed from the bucket if it’s available and accept the request. If the token is not available then the server rejects the request.

As requests are consuming tokens, we also need to refill them at some fixed rate and time, such that we never exceed the capacity of the bucket. Let’s consider an API that has a rate limit of 100 requests per minute. We can create a bucket with a capacity of 100, and a refill rate of 100 tokens per minute.

Please refer to the Understanding Rate Limiting Algorithms blog where the Token Bucket and other algorithms have been explained in detail.

Building a Springboot Application with API Rate Limiter

Create a new spring boot application from Spring Initializr with dependency on spring web module.

Unzip the downloaded project and import to your IDE. Let’s begin by adding the bucket4j dependency to our pom.xml

<dependency>
    <groupId>com.github.vladimir-bukhtoyarov</groupId>
    <artifactId>bucket4j-core</artifactId>
    <version>4.10.0</version>
</dependency>

We are going to implement a simple calculator REST APIs that can do operations like add and subtract.

@RestController
@RequestMapping(value = "/api/calculator")
public class CalculatorController {

    @GetMapping(value = "/add")
    public ResponseEntity<Calculator> add(@RequestParam int left, @RequestParam int right) {
        return ResponseEntity.ok(Calculator.builder()
                .operation("add").answer(left + right).build());
    }

    @GetMapping(value = "/subtract")
    public ResponseEntity<Calculator> subtract(@RequestParam int left, @RequestParam int right) {
        return ResponseEntity.ok(Calculator.builder()
                .operation("subtract").answer(left - right).build());
    }

}

Let’s ensure that our above APIs are up and running as expected. You can use the cURL or PostMan to make an API call.

curl -X GET -H "Content-Type: application/json" 'http://localhost:9090/api/calculator/add?left=20&right=30'
{"operation":"add","answer":50}

Now that we have APIs ready to consume, next let’s introduce some subscription plans with rate limits. Let’s assume that we have the following subscription plans for our clients:
  • Free Subscription allows 2 requests per 60 seconds.
  • Basic Subscription allows 10 requests per 60 seconds.
  • Professional Subscription allows 20 requests per 60 seconds.

Each API client gets a unique API key that they must send along with each request. This would help us identify the client and subscription plan linked.

public enum SubscriptionPlan {

    SUBSCRIPTION_FREE(2),
    SUBSCRIPTION_BASIC(10),
    SUBSCRIPTION_PROFESSIONAL(20);

    private int bucketLimit;

    private SubscriptionPlan(int bucketLimit) {
        this.bucketLimit = bucketLimit;
    }

    public int getBucketLimit() {
        return this.bucketLimit;
    }

    public Bandwidth getBandwidth() {
        return Bandwidth.classic(bucketLimit,
                Refill.intervally(bucketLimit,
                        Duration.ofMinutes(1)));
    }

}

Next we create a subscription service which will store the bucket reference for each of the API client in a memory.

@Service
public class SubscriptionService {

    private final Map<String, Bucket>
            subscriptionCacheMap = new ConcurrentHashMap<>();

    public Bucket resolveBucket(String subscriptionKey) {
        return subscriptionCacheMap.computeIfAbsent(
                subscriptionKey, this::getSubscriptionBucket);
    }

    private Bucket getSubscriptionBucket(String subscriptionKey) {
        return buildBucket(
                resolveSubscriptionPlanByKey(subscriptionKey)
                        .getBandwidth());
    }

    private Bucket buildBucket(Bandwidth limit) {
        return Bucket4j.builder().addLimit(limit).build();
    }

    private SubscriptionPlan resolveSubscriptionPlanByKey(
            String subscriptionKey) {
        if (subscriptionKey.startsWith("PS1129-")) {
            return SubscriptionPlan.SUBSCRIPTION_PROFESSIONAL;
        } else if (subscriptionKey.startsWith("BS1129-")) {
            return SubscriptionPlan.SUBSCRIPTION_BASIC;
        }

        return SubscriptionPlan.SUBSCRIPTION_FREE;
    }
}

Let’s understand the implementation. The API client sends an API key with the X-Subscription-Key request header. We use the SubscriptionService to get the bucket for this API key and check whether the request is allowed by consuming a token from the bucket.

In order to enhance the client experience of the API, we will add the following additional response headers to send information about the rate limit.
  • X-Rate-Limit-Remaining - number of tokens remaining in the current time window.
  • X-Rate-Limit-Retry-After-Seconds - remaining time in seconds until the bucket is refilled with new tokens.
We can call ConsumptionProbe methods getRemainingTokens and getNanosToWaitForRefill, to get the count of the remaining tokens in the bucket and the time remaining until the next refill, respectively. The getNanosToWaitForRefill method returns 0 if we are able to consume the token successfully.

Let’s create a RateLimitInterceptor and implement the rate limit code in the preHandle method instead of writing in every API method as we will have cleaner implementation.

@Component
public class RateLimiterInterceptor implements HandlerInterceptor {

    private static final String
            HEADER_SUBSCRIPTION_KEY = "X-Subscription-Key";
    private static final String
            HEADER_LIMIT_REMAINING = "X-Rate-Limit-Remaining";
    private static final String
            HEADER_RETRY_AFTER = "X-Rate-Limit-Retry-After-Seconds";
    private static final String
            SUBSCRIPTION_QUOTA_EXHAUSTED =
            "You've exhausted your API Request Quota. " +
            "Please upgrade your subscription plan.";

    @Autowired
    private SubscriptionService subscriptionService;

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        String subscriptionKey =
                request.getHeader(HEADER_SUBSCRIPTION_KEY);
        if (StringUtils.isEmpty(subscriptionKey)) {
            response.sendError(HttpStatus.BAD_REQUEST.value(),
                    "Missing Request Header: " +
                        HEADER_SUBSCRIPTION_KEY);
            return false;
        }

        Bucket tokenBucket = subscriptionService
                .resolveBucket(subscriptionKey);
        ConsumptionProbe consumptionProbe =
                tokenBucket.tryConsumeAndReturnRemaining(1);
        if (!consumptionProbe.isConsumed()) {
            long waitTime =
                    consumptionProbe.getNanosToWaitForRefill()
                            / 1_000_000_000;
            response.addHeader(HEADER_RETRY_AFTER,
                    String.valueOf(waitTime));

            response.setContentType(
                    MediaType.APPLICATION_JSON_VALUE);
            response.sendError(
                    HttpStatus.TOO_MANY_REQUESTS.value(),
                    SUBSCRIPTION_QUOTA_EXHAUSTED);
            return false;
        }

        response.addHeader(HEADER_LIMIT_REMAINING,
                String.valueOf(
                    consumptionProbe.getRemainingTokens()));
        
        return true;
    }
}

Finally, let’s add the interceptor to the InterceptorRegistry of Springboot so that the RateLimitInterceptor intercepts each request to our calculator API endpoints.

@SpringBootApplication
public class TokenBucketApplication implements WebMvcConfigurer {

   @Autowired
   @Lazy
   private RateLimiterInterceptor interceptor;

   public void addInterceptors(InterceptorRegistry registry) {
      registry.addInterceptor(interceptor)
            .addPathPatterns("/api/calculator/**");
   }

   public static void main(String[] args) {
      SpringApplication.run(TokenBucketApplication.class, args);
   }

}

Let invoke calculator API to see the behaviour.

curl -X GET -H "Content-Type: application/json" 'http://localhost:9090/api/calculator/add?left=20&right=30'
{"timestamp":"2020-12-25T12:43:43.239+0000","status":400,"error":"Bad Request","message":"Missing Request Header: X-Subscription-Key","path":"/api/calculator/add"}

The client has to send the API key within the http header otherwise the interceptor will not process the request. Let’s add the API key to the header and make the call.

curl -v -X GET -H "Content-Type: application/json" -H "X-subscription-key:A1129-12" 'http://localhost:9090/api/calculator/add?left=20&right=30'
* Connected to localhost (::1) port 9090 (#0)
> GET /api/calculator/add?left=20&right=30 HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Type: application/json
> X-subscription-key:A1129-12
>
< HTTP/1.1 200
< X-Rate-Limit-Remaining: 1
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Fri, 25 Dec 2020 12:46:06 GMT
<
* Connection #0 to host localhost left intact
{"operation":"add","answer":50}
* Closing connection 0

You can see the API key is added in the header, the API responds to our request and also it has added response header which shows how many rate is remaining for the API key.

Let’s make 2 more calls then we should see that we exhausted our rate for the free plan and returns 429 as response.

curl -v -X GET -H "Content-Type: application/json" -H "X-subscription-key:A1129-12" 'http://localhost:9090/api/calculator/add?left=20&right=30'
* Connected to localhost (::1) port 9090 (#0)
> GET /api/calculator/add?left=20&right=30 HTTP/1.1
> Host: localhost:9090
> User-Agent: curl/7.64.1
> Accept: */*
> Content-Type: application/json
> X-subscription-key:A1129-12
>
< HTTP/1.1 429
< X-Rate-Limit-Retry-After-Seconds: 51
< Content-Type: application/json
< Transfer-Encoding: chunked
< Date: Fri, 25 Dec 2020 12:49:11 GMT
<
* Connection #0 to host localhost left intact
{"timestamp":"2020-12-25T12:49:11.358+0000","status":429,"error":"Too Many Requests","message":"You've exhausted your API Request Quota. Please upgrade your subscription plan.","path":"/api/calculator/add"}
* Closing connection 0

It looks like we have successfully implemented the rate limiter using the Token Bucket algorithm. We can keep adding endpoints and the interceptor would apply the rate limit for each request.

As usual, the source code for the above spring boot implementation is available over on GitHub.

Featured Post

Your AI Sidekick: How Claude took over Pritee’s Repetitive tasks

  It was a classic Wednesday morning in our Bengaluru office . Pritee, one of our sharpest Project Managers, had just stepped out of a stake...