Secure Your APIs (Part 2): Rate Limiting and Validation

Secure Your APIs (Part 2): Rate Limiting and Validation

Recap

In the previous blog, we made our own proxy server to handle APIs. It is important to note that the proxy servers are NOT our main servers, and therefore we do not use proxy servers to handle database requests directly. They are used mainly for validation, caching, security protocols, etc. if you haven't read the first part, I recommend reading it because this is a continuation. Down below will be the link for Part 1.

Secure Your APIs: Part 1

You might not have realized that by creating a proxy server, you have also created your own Private API. And today we will cover the API Key Validation and one of client API access mechanisms called Rate Limiting.

API Key Validation

You created your own private API, but anyone can get your data if your API does not have a validation middleware. See this example in the link given below

Swagger API

If you click on authorize button, you'll notice that you need a special key to validate yourself in order to access the data using the API endpoint. This is how you secure your API endpoints, by creating access keys so that only authorized users/clients can get a successful response from the API request.

Execution

Let's create our own validation method. Open the "proxy-server" project created in the last blog.

proxy-server/routes/joke.js

const express = require('express');
const makeAJoke = require('../controller/make-a-joke');

const router = express.Router();

const customKeys = new Map(); // Create a Map Data Structure
customKeys.set('dark123',true); // map the 'dark123' string to "true" bool value

router.get('/joke', // request at /joke endpoint
(req, res, next)=>{ // insert this middleware to check access key
    const key = req.get('X-KEY'); // get the key mentioned by user from the headers
    if(customKeys.has(key)){ // if the user given by the user matches with your map
        next(); // go to the next handler which is the makeAJoke controller
    }else{
        const err = new Error('Invalid API Key');
        next(err); // else create an error and pass that to the controller
    }
}, // API Key Validator
makeAJoke); // controller

module.exports = router;

Here we created an access key for our API called 'X-KEY' and 'dark123' as a valid value for the access key our server. Therefore, users need to provide this access key in their request like this:

const response = await fetch(`${base_URL}/api/v1/joke`,{
    headers:{
        //core headers if any,
        'X-KEY': 'dark123'
    }
})

Only then they can access our endpoint. Let's verify this mechanism using Postman API.

Testing

Return Invalid API Key' because the 'X-KEY' is not provided in request headers. Will also show the same error if the 'X-KEY' provided is not the one we set in our server.

But if we provide the right access key,

Bingo! we will be able to access the data and successful response. This was the simplest validating method just to show you how you manage to pull it off too, you might want to try validating your tokens or keys using hash functions instead, for greater security.

Rate Limitation

Have you ever seen this type of message/response ?

This happened because you tried to call the API a greater number of times than it's decided limit in a given time interval after the first most recent call.

The limit and the time interval are set by the server. Let's see an example:

Imagine you ask your mom for food, and she says, "You ate just an hour ago, wait an hour more, only then I'll make you something."

Realize the concept? Your mom is telling you that you can only eat something once in every 2 hours. This is the exact concept of rate limiting, you can only send a request X number of times in a Y interval of time.

Let's see the practical implementation

Execution

Open your terminal in you root folder and enter:

npm install express-rate-limit

Now open your project in vs code and follow the steps:

proxy-server/routes/joke.js

const express = require('express');
const rateLimit = require('express-rate-limit'); // require the package
const makeAJoke = require('../controller/make-a-joke');

const router = express.Router();

const limiter = rateLimit({
    windowMs: 10*1000, // time limit threshold for number of requests defined below
    max: 2, // amount of requests you can make in the time limit defined above
    message: "Too many requests! Try again in 10 seconds", // error message
    headers: true
})
// this means you can only call the API a maximum of 2 times in a 
// 10 second time frame, if you call it a 3rd time, you will 
// get an error with the message defined above

const customKeys = new Map();
customKeys.set('dark123',true);

router.get('/joke',
 limiter, // add rate limiter middleware before key validatory
(req, res, next)=>{
    const key = req.get('X-KEY');
    if(customKeys.has(key)){
        next();
    }else{
        const err = new Error('Invalid API Key');
        next(err);
    }
},
makeAJoke);

module.exports = router;

And done ! Test your limiter by running the server on any port of your choice, and try to send the request from Postman 3 times in a 10 second time frame.

This is how you implement rate limiter and access key validation technique for your API

Summary

API access key & validation

  • A private API access key to authorize the user making the API request

  • Implemented using hash functions for greater security.

Rate Limiting

  • You can only make a request X number of times in a Y interval time frame.

Questions

Do you implement validating in the proxy server or main server?

The answer is both because the API key needs to be secret, therefore the actual request is sent by either hashing at the client side itself and validating at the proxy server OR created and hashed at proxy server and validated by the main server. Choose your own method depending on the level of security you need.

You know the concept of rate limiter. What if 3 different users try to access the key at the same time? Each user will call the API only once but the API at the endpoint is called for a total of 3 times therefore it will show the error message to the 3rd user. How will you plan to handle that? Comment if you know the answer and if you don't, don't worry because I'll provide the answer soon. Till then brainstorm the solution!

Thanks for reading it through. I hope you liked it and make sure to like if you did and leave a comment for any suggestions/questions/discussions. Happy coding :)