Protecting Private API Keys using a Simple Proxy Server
Tags: server, deno, javascript, api
Published: 2022-05-28 22:03:02

Two common mistakes developers make when using a private API key in their application are:
- Including the API key in the source code
- Including the API key in client-side HTTP requests

Revealing private API keys and authorization tokens to users through client-side HTTP requests and in your source code exposes you and your application to a high risk of abuse. If a malicious user uses your API key for their application, it can cost you money and potentially prevent you from using the API by exceeding your usage limits.

To avoid this, it’s recommended that you perform any requests that require authentication from the back-end of your application (a server) rather than the browser. However, if you need to make requests on the fly on the client-side, a quick way to accomplish this more securely is to use a proxy server to wrap requests with the necessary headers/query parameters containing your keys.


## What is a Proxy Server?

A proxy server acts as a middle-man between your application and the API. There are 3 steps:
1. You send a request to the server.
2. The server sends that request to the API.
3. The server responds to your request with the response from the API.

The proxy server I’m going to demonstrate here is only slightly more secure than using your keys in the browser as, without further precautions (such as domain restrictions), other users could simply make requests through your proxy. I’ll explain further precautions you can take at the end of this post.


## Basic Proxy Server Example

Here’s an example proxy server I wrote using Deno. I used Deno for because Deno Deploy is offering free hosting while it's in public beta (as of May 2022). If you visit [Deno Deploy](https://deno.com/deploy) and start a playground project, you can try this out for yourself using this code!

If you're using Node, the same principles apply, but you'll have to rewrite the code to use Express or another Node-based web application framework.

This server makes requests to the Twitter API. To authenticate my requests, I must include an “Authorization” header with a bearer token as its value. I'll send requests to the proxy server and it will attach the Authorization header and send the request to the API.

To make my requests through the proxy, I’ll replace the API domain with my server's domain.

Here's an example: 
- My server is hosted at `https://my-project.deno.dev`
- I want to send a request to the Twitter API at `https://api.twitter.com/2/tweets?ids=1261326399320715264`

To send the request via my proxy server from my application, I would use this combined URL: `https://my-project.deno.dev/2/tweets?ids=1261326399320715264`


```
import { serve } from "https://deno.land/std@0.137.0/http/server.ts";

const bearer = "Bearer MYBEARERTOKEN";

async function handler(req) {
  const url = new URL(req.url);
  const pathname = url.pathname;

  // Replace server domain with Twitter API domain for the proxy request
  const fetchURL = `https://api.twitter.com${pathname}${url.search}`;

  // Only make proxy request if there is a URL path
  if (pathname.length > 1) {
    const res = await fetch(fetchURL, {
      headers: { Authorization: bearer }
    });

    // If there is a response body, send it as text to the application.
    if (res.body) {
      return new Response(await res.text(), {
        headers: { "Access-Control-Allow-Origin": "*" }
      });
    }
  }
  // If there is no URL path, display "Hello World"
  return new Response("Hello World");
}

serve(handler);
```


## Further Precautions

As I mentioned earlier, this simple proxy server will prevent users from seeing your private API keys in your source code and HTTP requests, but a user might still make requests using your proxy server.

A precaution you can take to prevent this is to restrict requests from other domains. Simply change the Response header "Access-Control-Allow-Origin" from "*" to the hostname you want to accept requests from. Example: `{ "Access-Control-Allow-Origin": "https://www.google.com" }`

And that's it! If you have any thoughts on this method of protecting API credentials or other precautions you recommend, please mention them in the comments below!