Categories
API Essentials Fastify Node.js

How to securely call an authenticated API from your front end

5 min read

Your front end needs to access data from an API which requires an API key. If you put this API key in your client side JavaScript, you know that anyone viewing your website could view this API key (with a little help from their browser’s Developer Tools). This doesn’t seem secure at all, but what can you do instead?

Only applications running on a server – i.e. a back end – should have access to secrets like an API key. This means that requests with an API key can only be made from the server side. The thing is, you want your front end to be able to request and use data from the API in a secure way.

One way of achieving this is to create a "proxy" server. Instead of directly calling the API, your client side JavaScript will make requests to the proxy server. The proxy server can add an API key to every request and forward it on to the API. This keeps the API key secure and away from your front end.

In this article I’ll show you how to use the Fastify framework and the fastify-http-proxy plugin to create a proxy server.

The full code for this article is on GitHub.

This article uses ECMAScript (ES) module syntax. ES modules are well supported in v12.20.0 / v14.13.0 (and higher) releases of Node.js. Learn more about ES module support in Node.js.

Jump links

Request flow with a proxy server

Let’s assume that we have some client side JavaScript running on a web page – it could be a React application, or "vanilla" JavaScript (no framework or library). This client side JavaScript needs to retrieve data from an API which requires an API key to be sent in the request.

As we don’t want our client side JavaScript to contain the API key for security reasons, we’re going to create a proxy server in Node.js which can receive a request from the client side JavaScript (made with fetch, or a request library like Axios). This proxy server will add the required API key to the request and forward it on to the API server.

The request flow from the client (JavaScript running on a web page in a user’s browser) through to the API server will look like this:

Request from client side JavaScript to our proxy server
 ↓
Proxy server receives request, adds the API key, forwards request to API server
 ↓
API server receives request, sends response back to proxy server

When the proxy server receives a response from the API server it will send it back to the client. At no point will the API key be exposed to the client side JavaScript.

Instead of making requests to https://some-api.com/some/path from our client side JavaScript, we’ll now make requests to our proxy server: https://my-proxy.com/some/path. Neat, right?

Create a server with Fastify

We’re going to use the Fastify framework and the fastify-http-proxy plugin to create our proxy server in Node.js.

First let’s install the dependencies which our proxy server application will require:

npm install fastify fastify-http-proxy

We’re now going to create and configure a Fastify server instance:

// src/server.js

import createFastifyServer from "fastify";

/**
 * Create a Fastify server instance with logging enabled.
 * Fastify uses the library `pino` for logging.
 *
 * @see https://www.fastify.io/docs/latest/Logging/
 * @see https://github.com/pinojs/pino/
 */
const fastify = createFastifyServer({
	logger: true,
});

try {
	/**
	 * Make use of top-level `await` i.e. outside of an `async` function.
	 *
	 * @see https://nodejs.org/docs/latest-v14.x/api/esm.html#esm_top_level_await
	 */
	await fastify.listen(3000);
} catch (error) {
	fastify.log.error(error);
	process.exit(1);
}

If we run this code (node src/server.js), we’ll have an HTTP server listening on port 3000.

Our server doesn’t provide any endpoints which you can make a request to, so making a request to http://localhost:3000/ will result in a 404 error response. We need to add and configure the fastify-http-proxy plugin in order for our server to be able to handle requests.

Add and configure the fastify-http-proxy plugin

We want to configure our Fastify server to proxy requests which it receives to https://some-api.com. In order to do this, let’s import the fastify-http-proxy plugin and configure it:

// src/server.js

import fastifyHttpProxy from "fastify-http-proxy";

/**
 * Register and configure the `fastify-http-proxy` plugin.
 *
 * This plugin supports all the options of `fastify-reply-from`,
 * as well as a few additional options e.g. `upstream`.
 *
 * @see https://github.com/fastify/fastify-http-proxy#options
 * @see https://github.com/fastify/fastify-reply-from
 */
fastify.register(fastifyHttpProxy, {
	upstream: "https://some-api.com",
	undici: true,
});

Our Fastify server is now configured as a proxy server. It will forward all requests it receives to https://some-api.com (our "upstream" server).

Our proxy server will use the HTTP client library undici to make requests to the upstream server. The undici library is a dependency of fastify-reply-from, which fastify-http-proxy is using under the hood. undici is much faster than the native HTTP client provided by Node.js.

Now that we have our proxy server set up, we need to configure it to add an API key to proxied requests.

Add an API key to proxied requests

There are several different ways that APIs can implement authentication. One of the most common methods is for the client to pass an API key in the request. Typically APIs require the API key to be sent in a request header e.g. X-Api-Key: abc123. Some APIs might require the API key in a query string parameter e.g. ?apiKey=abc123.

fastify-http-proxy accepts a replyOptions object which it passes through to fastify-reply-from. These options give us full control to modify requests and responses as they pass through our proxy server.

Let’s take a look at how we can modify requests and add an API key before our proxy server forwards it on to the API server (our "upstream").

HTTP request header

In order to add an API key to the HTTP request headers, we’re going to set a replyOptions.rewriteRequestHeaders function. We will access our API key from an environment variable and set it as the value of an X-Api-Key request header. This code builds on our initial configuration for the fastify-http-proxy plugin:

// src/server.js

const CONFIG = {
	apiKey: process.env.API_KEY,
};

fastify.register(fastifyHttpProxy, {
	upstream: "https://some-api.com",
	undici: true,
	replyOptions: {
		rewriteRequestHeaders: (originalRequest, headers) => {
			return {
				/**
				 * Preserve the existing request headers.
				 */
				...headers,
				/**
				 * Add the header which the API we're proxying requests
				 * to requires to authenticate the request.
				 */
				'X-Api-Key': CONFIG.apiKey,
			};
		},
	},
});

With a little extra configuration our server is now adding an API key to every request that it is proxying.

While X-Api-Key is a commonly used request header name, the API you are making requests to might require a different header e.g. Authorization: Bearer <TOKEN>. The replyOptions.rewriteRequestHeaders option allows us to add any request headers that we need.

View the full code for an example proxy server which authenticates using an API key request header

URL query string

I do not recommend that you design your own APIs to accept an API key via a URL query string. An API key is a "secret", just like a password is. When you put an API key in a URL it is much easier to accidentally leak it than if you send it via a request header e.g. by an accidental copy and paste, or by logging it in server request logs.

Unfortunately, some APIs do require you to send an API key in the URL query string. If this is the only way for you to authenticate with the API you are making requests to, you can use the replyOptions.queryString option provided by fastify-http-proxy.

Conclusion

In this article we’ve learnt how we can use Fastify and the fastify-http-proxy plugin to proxy requests and add an API key to them. This allows us to keep our API key secure and away from our client side JavaScript.

If your front end and back end proxy server are accessed via different origins, you might need to add CORS headers to the response which your proxy server sends back. You can read more about CORS in this article.

While fastify-http-proxy is very powerful and allows us to set up a proxy with minimal configuration, there are cases in which you might want to take a different approach e.g.

  • You need to proxy complex API requests
  • You want to create your own abstraction over another API
  • You have an existing back end Node.js server application

In these cases you might want to consider creating your own API endpoints which then make requests to an upstream API. The node-fetch library is a popular choice for making requests from Node.js. However, if you are interested in the features offered by undici, I recommend keeping an eye on the undici-fetch library. It is being developed as a WHATWG Fetch implementation based on undici.

7 replies on “How to securely call an authenticated API from your front end”

Great article. Do you have any examples on the usage of replyOptions.queryString ? I want to add an api key to proxied requests, unfortunately the external api requires the key in the query parameter. How do I keep the request queries but just add on the api key?

Hi Mattias,

Thank you, I’m glad you found this article helpful.

I’ve been looking through the docs for fastify-http-proxy and fastify-reply-from (the library which fastify-http-proxy uses under the hood). Unfortunately I can’t figure out a way to preserve the original query string from the request and add on an API key.

I’ve dropped a message in the Fastify Discord and I’ll let you know if someone has a solution for this.

Hi Simon!

Thank you for your reply. I managed to find a solution, even though it’s not the most graceful one:
req.raw.url = `${req.raw.url}&apikey=${CONFIG.apiKey}`;
Somehow altering the request raw url works, but altering req.query does not. Strange.
Thank you for taking your time to reply and help me with my issue, and thank you again for this very educational post.

Kindly, Mattias

Thanks for sharing your solution, Mattias! I’ll include it in the article so that it’s visible to help anyone else who needs to do this.

Thanks for this article! Adding “static” headers works fine in the rewriteRequestHeaders but adding for example information contained in the Session does not work.. (For example, adding an Authorization header containing user-specific credentials…) Do you know a solution for this?

Hi Stelios,

What specifically isn’t working? Are you trying to access session cookie data?

Comments are closed.