Introduction
Have you ever been hit with the message “login error you have reached maximum retry, please try again later?” Yes. So many times, most especially when you have forgotten your password and have tried too many times.
Well, this serves as one of the security measures for limiting brute force attacks. This could be implemented on a login page, registration page, forgot password page or reset password or the whole application. You can as well limit access to certain API endpoints until after some time.
If you are a regular internet surfer, you could be wondering how you can be able to access the same site with another browser or device but couldn’t access it that particular device until after the stipulated time.
Well, you are not alone, I was once in your shoes until I learnt why. In this article, we will talk about various techniques of rate limiting and we will implement an auth example using react js, node js and express framework. Grab a cup of coffee or bottle of water, let’s take a spin.
In-depth Video Tutorial
If you want to watch the full implementation with more detailed explanation, watch the video below;
What is rate limiting?
Rate limiting is a policy that allows you to control the number of requests made by a user to access backend APIs and it secures your APIs against malicious attacks like brute force. It prevents the user from making unwanted requests to backend API resources.
Rate limiting gives the developer the power to control how many requests a server can handle from a user at a given period of time and if the requests exceed the maximum allowed requests, subsequent requests are spoofed.
When you are developing a software application, it’s a good idea to follow security best practices to reduce the risk of hacking or malicious attacks.
It is recommended that you implement rate limit on authentication endpoints such as login attempts, registration page, reset and forgot password pages in order to track and limit the number of times someone can attempt to access this endpoint. This significantly reduces a hacker’s chance of trying to guess your password using a brute force attack.
Rating limiting is not limited to authentication endpoints only. In fact, most API offering companies use this technique to limit user API calls. This could let them track the number of times a user accesses a particular endpoint using an API given a particular period of time so as to enforce billing.
As a developer you could also, enforce rate limited on any resource as the power is in your hands.
Rate limiting constraints
Rate limiting can be implemented by the following constraints;
User IP address
User location
User id or user device id or user API key
Different Algorithms for implementing rate limiting
I won’t do justice to this article if I don’t talk about different algorithms involve in designing a rate limiting system. Below is a brief summary of each algorithm with links to read more.
Fixed Window Counters
This is undoubtedly the simplest technique for implementing rate limiting. In this technique, we track the number of requests made in a fixed-size time window. If the number of requests in any time window exceeds the defined ration, further requests from that client for the remainder of that window are blocked.
On subsequent requests, the counter is checked to see if the defined limit is not exceeded in a given window, then process the request and increment the counter else drop the request. Clients will have to wait till the window time frame is expired before making any successful request.
For instance, a client can only be allowed to make 10 requests in 30 min window time frame. If the requests are exhausted, so long as the time is not yet expired, subsequent requests will be blocked until after the time expires and another time frame will start counting on subsequent requests.
Sliding Window Logs
The sliding logs technique keeps log of the timestamps for each user’s requests. This can be simply implemented with Redis or HashMap and may be sorted based on time in order to improve operations. Let’s assume a 1-minute (60 sec) rate limiting window.
Remove all the requests older than 1 minute leaving just the requests made in the current minute
Check if the requests in the current minute exceed the limit. If yes drop the request else process the request and log it
Update sorted set TTL with each update so that it’s cleaned up after being idle for some time.
This technique is much more efficient than the fixed window counter but it consumes more memory.
Sliding Window Counters
This mechanism tries to fix the inefficiencies of both sliding window logs and fixed window counters by splitting the rate-limit window into smaller windows and track counters across those smaller windows instead of logging all the requests.
For instance, instead of maintaining counters across a one-minute window, we can use one-second windows (so 60 sub-windows for a minute interval). To determine whether to process or drop a request, we take the sum of the counters of all previous 60 sub-windows from the current time.
Token Bucket
In token bucket algorithm, each user receives a certain number of tokens which are periodically updated based on the timestamp. When a user makes the first request, a log is created based on the constraints above and a number of tokens specified. On subsequent requests, the log is retrieved to check if the total tokens assigned to that specific user is not exhausted then process the request and deduct a token else reject & throw an error.
Leaky bucket
The leaky bucket algorithm keeps a finite number of requests for a given user in a queue manner and execute them at a constant rate. It uses queue to enforce the limit based on the queue size in a first-in first-out (FIFO) approach. Requests are taken out of the queue and processed at a constant rate. If the requests exceed the queue size, those incoming requests will be dropped until the requests in the bucket are process. It works at a constant rate no matter the amount of traffic that a server receives.
For instance, if the limit is 5 requests per minute, then the queue would only be able to hold 5 requests per time.
For an in-depth discussion on all the techniques, checkout the links below
Photo credit
FreeVector.com
Implementing Rate Limit in react js, node js and express API
Server-side implementation on Node.js and express.js
To implement rate limiting on a node js express js server, we will make use of a third-party library known as express-rate-limit which has done most of the heavy lifting for us.
Express-rate-limit is a basic rate-limiting middleware for Express. It limits repeated requests to public APIs and/or endpoints such as authentication routes.
Boot up your cmd and navigate to your server directory, set up a basic express server and install the following dependency
npm install express-rate-limit
Firstly, create a directory under the server directory known as middleware
Create a file known as index.js inside the middleware directory
Copy and paste the following code.
const rateLimit = require('express-rate-limit');
const loginRateLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 min in milliseconds
max: 5,
message: ‘Login error, you have reached maximum retries. Please try again after 30 minutes',
statusCode: 429
headers: true,
});
module.exports = { loginRateLimiter }
windowMs: The time frame for which requests are checked/remembered. Also used in the Retry-After header when the limit is reached.
max: The maximum number of connections to allow during the window before rate limiting the client.
message: The response body to send back when a client is rate limited. It can be string, json, or any other value that Express's response.send method supports.
statusCode: The HTTP status code to send back when a client is rate limited.
This is just the basic configuration; you can find more about this library here
Express-rate-limit
There are different ways you can use express-rate-limit within our application as shown below;
1. Using globally across all routes
If you want to use it globally, open your main server file where you have configured your express app and import the loginRateLimiter middleware function from middleware as shown below;
const { loginRateLimiter } = require(“./middleware”)
app.use(loginRateLimiter)
app.get(“/api/login, (req, res) =>{
const {username, password} = req.body
const CORRECT_PWD = “1234”
const CORRECT_USER = “demo”
if(username.toLowerCase() === CORRECT_USER && password === CORRECT_PWD){
return res.send(“Login successful”)
}
return res.send(“Wrong login credentials”)
})
Open your postman or CURL and try to make wrong or correct requests up to 5 times and the sixth time, you will receive an error response from our loginRateLimiter middleware like ‘Login error, you have reached maximum retries. Please try again after 15 minutes’
The above implementation is how we use express-rate-limit globally but we might have a problem as not all routes are login routes and what if we have more than rate limit middle to apply based on different routes? That’s where per route basis comes in.
2. Using express-rate-limit on a specific route
Copy the code below and replace the code above, you will notice on the login routes that we applied the *loginRateLimiter * on the login route instead of using it globally on all endpoints. With this approach we can use as many different rate limiters as we want in our application.
const { loginRateLimiter } = require(“middleware”)
//app.use(loginRateLimiter)
app.get(“/api/login, loginRateLimiter, (req, res) =>{
const {username, password} = req.body
const CORRECT_PWD = “1234”
const CORRECT_USER = “demo”
if(username.toLowerCase() === CORRECT_USER && password === CORRECT_PWD){
return res.send(“Login successful”)
}
return res.send(“Wrong login credentials”)
})
Open your postman or CURL and try to make wrong or correct requests up to 5 times and the sixth time, you will receive an error response from our loginRateLimiter middleware like ‘Login error, you have reached maximum retries. Please try again after 15 minutes’
Everything will still work like before. Hey, I want to keep this article very simple but at the same time I don’t want to compromise the implementation. I’m sorry for taking your time, lets handle just the client app in react js. I promise this is the last part as we’re done with server-side implementation.
Client-side implementation with react js
For the client-side implementation with react js where I have developed a login form and limit access to 5 requests per 15 min window size, you can get the repo here;
Get the whole repo both client & server on Gthub repo
Continue reading;
Side Note
What is unique about me is that, in all my articles I always try my best to demonstrate how it works in a real-life application. So, would you love to miss an article from someone like me, just do me a favor and follow me here to never miss any concise & precise article.
Please, don’t forget to also like, comment, share, subscribe to my Youtube channel and turn on notification. This will make me happy. Thank You in advance. PS.
Summary
Security is very paramount in every application and API calls are expensive.
Rate limiting is a policy that helps protect your APIs and services from excessive use either from bad actors trying to intentionally abuse your server API service by limiting the number of requests your server API in a given duration of time. This tutorial discusses different techniques used to implement rate limiting on API endpoints as well as the need to do so.
In this article, we implemented a simple login page with rate limiter forbidding a user to make further requests until the window fixed size is expired.
Rate limiting if done properly can serve as one of the security measures by reducing the number of brute force attacks well as prevent overflooding your server with unnecessary requests.
I demonstrated how it can be implemented in a real-life application but you can build a robust rate limiting feature for your app’s need using redisdb which makes it faster to read and write data.
In-depth Video Tutorial
If you want to watch the full implementation with more detailed explanation, watch the video below;
Top comments (2)
Thanks Peter. Help me a lot !
Very nice guide