Secure Your APIs (Part 1) : Leakage and Proxying

Chinmay PandyaChinmay Pandya
4 min read

API Leakage

Everybody uses APIs in their applications but did you know that your API request might contain sensitive information which you might not wanna share with others, such as API keys/tokens.

API keys/tokens are a way in which you can authorize yourself and make valid API requests. These are passed in mostly headers but also sometimes query params, depends on the API.

const response = await fetch(url, {
    headers: {
        'Content-Type': 'application/json',
        'API_KEY': 'API_KEY_VALUE'
    }   
}

// OR

const response = await fetch(`${url}?&apikey=${API_KEY}`);

This is how a normal get request with headers/params looks like. Notice how your API key is being exposed ? This is why you almost never call APIs in the client side. Well then how would you fetch/post your data ? You guessed it right !, using backend servers.

Proxy Servers

Proxy means letting somebody else handle your task in your absence. Similarly when you create a server for handling tasks on behalf of a client/another server, It is called a Proxy Server. It is a great way to hide sensitive information from the client side and instead manage everything from the server side.

Let me show you how to make a proxy server and make secure API requests.

Setup (Terminal)

Open your terminal and enter the following lines one by one

mkdir proxy-server
cd proxy-server

This will be our root folder.

npm init -y
npm i express cors dotenv axios
touch index.js
mkdir routes
mkdir controllers
mkdir middleware

Now open vs code here

code .

Execution (VS Code)

proxy-server/index.js

const express = require('express');
const cors = require('cors');
const middleware = require('./middleware/middleware');
const joke = require('./routes/joke');

require('dotenv').config();

const app = express();
app.use(cors());
app.set('trust proxy',1); // tells the client to trust this proxy server


const port = process.env.PORT || 5000;

app.use('/api/v1',joke); // custom endpoint managed by "joke" route

app.listen(port, (req,res)=>{
    console.log(`Server running on port ${port} !`);
});

app.use(middleware.notFound); // custome error handling middlewares
app.use(middleware.errorHandler);

proxy-server/routes/joke.js

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

const router = express.Router();

router.get('/joke', makeAJoke);

module.exports = router;

proxy-server/controllers/make-a-joke.js

const axios = require('axios');

const BASE_URL = 'https://v2.jokeapi.dev/joke/Dark?'; 
// intentionally not hidden

const generate = async (req, res, next) => {
    try{
        const params = new URLSearchParams({
            [process.env.API_KEY]: process.env.API_KEY_VALUE,
            // you can also pass query params if any
        });

        const {data} = await axios.get(`${BASE_URL}${params}`);
        // you can use the same trick for passing your api key in 
        // headers instead of params as well

        return res.json(data);
    }catch (err){
        return next(err);
    }
}

module.exports = generate;

proxy-server/middleware/middleware.js

function notFound(req, res, next) {
    res.status(404);
    const error = new Error(`๐Ÿ” - Not Found - ${req.originalUrl}`);
    next(error);
  }

  /* eslint-disable no-unused-vars */
 function errorHandler(err, req, res, next) {
    /* eslint-enable no-unused-vars */
    const statusCode = res.statusCode !== 200 ? res.statusCode : 500;
    res.status(statusCode);
    res.json({
      message: err.message,
      stack: process.env.NODE_ENV === 'production' ? '๐Ÿฅž' : err.stack
    });
  }

module.exports = {
    notFound,
    errorHandler
};

Create a ".env" inside your root folder and follow he below steps

proxy-server/.env

NODE_ENV=development
PORT=5000
API_KEY=type # Just for example
API_KEY_VALUE=single # Just for example

Testing

Great ! Your proxy serve has been successfully set up ! The server you just created follows an MVC (Model View Controller) pattern. Open your terminal again in the root folder and run:

node index.js

Open http://localhost:5000/api/v1/joke on your browser.

Question

Why not just use .env in the client side ? Because that way the API_KEY is hidden only on the code side, not on the browser, therefore someone can still see your request parameters or headers from the client side.

In this way you only need to make a simple fetch request to your proxy server and the rest will be handled by the server.

const response = await fetch('http://localhost:5000/api/v1/joke');

Now nobody can read your API keys or params from the client side as they are simply "Not there".

Part 2 (Disclaimer)

  • API Validator

  • Rate Limiting

  • Caching

Summary

To summarize, API proxy servers are used to make private requests and implement confidentiality. Your request will no longer be visible on the client side and you can avoid API Leakage. You now also have the option to optimize your API using "Caching", "Polling" or "Validator" techniques (will be covered in part 2).

Thanks !

This was the 1st part of the 2-part blog post. The next part will cover "creating your own Private API Validator*,* Request Rate Limiter and Response Caching Techniques". Thanks for reading it through. I hope you liked it :)

Leave a like if you did and comment your questions/suggestions if you have any. I am open to any discussions !

0
Subscribe to my newsletter

Read articles from Chinmay Pandya directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Chinmay Pandya
Chinmay Pandya

๐Ÿ‘จโ€๐Ÿ’ป About Me: Hii, I'm Chinmay, a passionate and organized computer science student dedicated to lifelong learning and content creation. My journey in technology began with a curiosity-driven exploration of software development, cloud computing, and data science. ๐ŸŒŸ Blog Story: Driven by a desire to share knowledge and inspire others, I founded Hashnode, a platform where I chronicle my experiences, insights, and discoveries in the world of technology. Through my blog, I aim to empower fellow learners and enthusiasts by providing practical tutorials, thought-provoking articles, and engaging discussions. ๐Ÿš€ Vision and Mission: My vision is to foster a vibrant community of tech enthusiasts who are eager to learn, collaborate, and innovate. I am committed to demystifying complex concepts, promoting best practices, and showcasing the limitless possibilities of software development, cloud technologies, and data science. ๐ŸŒ Connect with Me: Join me on this exciting journey of exploration and growth! Follow Me for valuable resources, tutorials, and discussions on software development, cloud computing, and data science. Let's bridge the gap between theory and practice and embrace the transformative power of technology together.