Securing API Keys in React Apps with Netlify Functions: The Ultimate Guide

Cover Image

If you‘re building modern web applications with React, chances are you‘ll need to integrate with third-party APIs and services. Whether it‘s processing payments with Stripe, displaying maps with the Google Maps API, or analyzing data with a machine learning service, external APIs provide powerful functionality to enhance your apps.

However, working with APIs often requires API keys to authenticate requests and track usage. These keys are like passwords and need to be kept secret. Exposing them in your client-side JavaScript code is a major security risk.

In this in-depth guide, we‘ll look at how to use Netlify Functions to securely store and access API keys in your React applications. By moving sensitive data to serverless functions, you can ensure that keys are never exposed to users, while still allowing your frontend to make authenticated requests to external services.

The Problem with API Keys in Client-Side Code

To understand why we need a solution like Netlify Functions, let‘s look at the risks of including API keys in client-side code.

Here‘s an example of what NOT to do. Let‘s say you‘re using the Stripe API to process payments in your React app. You might be tempted to include the Stripe secret key directly in your code like this:

const stripeClient = new Stripe(‘sk_live_51I8sNsJYbJ2P9wQo9h1b3c2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z‘);

async function processPayment(amount) {
  const paymentIntent = await stripeClient.paymentIntents.create({
    amount,
    currency: ‘usd‘
  });

  return paymentIntent.client_secret;
}

The problem here is that the Stripe secret key (sklive…) is included directly in the client-side code. Anyone using the app could inspect the source code, find the key, and use it to make unauthorized requests to the Stripe API.

This is not just a theoretical risk. There have been numerous real-world cases of API keys leaking through client-side code. In 2020, security researchers found over 25,000 Google API keys exposed in Android apps, potentially allowing attackers to access sensitive user data or rack up large bills on Google Cloud services.

So what‘s the alternative? That‘s where serverless functions come in.

How Netlify Functions Keep API Keys Secure

Netlify Functions provide a way to run server-side code in response to HTTP requests, without managing a dedicated backend server. They‘re built on top of AWS Lambda, but with a simpler development experience and tighter integration with the rest of the Netlify platform.

The key benefit of using Netlify Functions to secure API keys is that they execute on the server, not in the user‘s browser. This means you can safely include sensitive data like API keys in your function code, and they‘ll never be exposed to clients.

Here‘s a simple example of a Netlify Function that makes a request to the Stripe API using an API key stored in an environment variable:

// functions/create-payment-intent.js

const Stripe = require(‘stripe‘);

const stripe = Stripe(process.env.STRIPE_SECRET_KEY);

exports.handler = async (event) => {
  const { amount } = JSON.parse(event.body);

  const paymentIntent = await stripe.paymentIntents.create({
    amount,
    currency: ‘usd‘
  });

  return {
    statusCode: 200,
    body: JSON.stringify({ client_secret: paymentIntent.client_secret })
  };
};

In this example, the Stripe secret key is loaded from the STRIPE_SECRET_KEY environment variable. Netlify provides a secure way to manage environment variables for your functions, so you don‘t have to include them in your code.

The frontend can then make a request to the /functions/create-payment-intent endpoint to create a new payment intent, without ever having access to the secret key:

async function processPayment(amount) {
  const response = await fetch(‘/functions/create-payment-intent‘, {
    method: ‘POST‘,
    body: JSON.stringify({ amount })
  });

  const { client_secret } = await response.json();

  return client_secret;
}

By moving the Stripe integration to a serverless function, we‘ve ensured that the secret key is never exposed in client-side code. The frontend only has access to the client_secret returned by the function, which is safe to include in client-side requests.

Architecture Overview

Here‘s a high-level diagram showing how a React app can securely access external APIs using Netlify Functions:

Architecture Diagram

  1. The React frontend makes a request to a Netlify Function endpoint (e.g. /functions/create-payment-intent).
  2. The Netlify Function loads sensitive data like API keys from environment variables.
  3. The function makes a request to the external API using the API key and returns a response to the frontend.
  4. The frontend uses the response data to update the UI or make further requests to the external API.

By routing all requests through Netlify Functions, we can ensure that sensitive data is never exposed to the client. The functions act as a secure proxy between the frontend and external APIs.

Advanced Use Cases and Best Practices

While the basic pattern of using Netlify Functions to secure API keys is straightforward, there are several advanced use cases and best practices to consider.

Secret Management with Netlify Environment Variables

Netlify provides a secure way to manage environment variables for your functions via the Netlify UI or CLI. You can define variables in your netlify.toml file or in the Netlify UI under Settings > Build & deploy > Environment.

Here‘s an example of defining environment variables in netlify.toml:

[context.production.environment]
  STRIPE_SECRET_KEY = "sk_live_51I8sNsJYbJ2P9wQo9h1b3c2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z"

[context.deploy-preview.environment]
  STRIPE_SECRET_KEY = "sk_test_51I8sNsJYbJ2P9wQo9h1b3c2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z"

This allows you to specify different values for environment variables depending on the deploy context (e.g. production vs. deploy previews).

It‘s important to note that environment variables are still just one part of the security puzzle. As Netlify CTO David Calavera points out:

The rule of thumb is that environment variables are a mechanism to separate secrets from code, but they don‘t protect those secrets. You still need to restrict access to the APIs, databases, and services that those secrets have access to.

On-Demand Function Deploys for Improved Security

An advanced technique for further enhancing the security of your Netlify Functions is to deploy them on-demand, rather than as part of your main site deploy.

By deploying functions separately from your frontend code, you can ensure that changes to your functions (e.g. updating an API key) don‘t require a full redeploy of your site. This helps limit the blast radius if a key is compromised and needs to be rotated quickly.

Netlify‘s On-Demand Builders feature allows you to trigger function deployments via webhooks, making it easy to integrate with key rotation workflows.

Comparing Netlify Functions to Other Approaches

Netlify Functions are just one approach to securing API keys in React apps. Other common techniques include:

  • Backend proxies: Running your own backend server to handle API requests and store keys securely.
  • Oauth flows: Using Oauth to authenticate users and generate short-lived access tokens for API requests.
  • Client-side encryption: Encrypting sensitive data with a user-provided key before sending it to the backend.

Each approach has its own tradeoffs in terms of complexity, performance, and security. Netlify Functions offer a simple, serverless solution that integrates well with the Netlify platform, but may not be suitable for all use cases.

As a general rule, the more sensitive the data you‘re working with, the more you should err on the side of server-side security. Anything that would be dangerous if exposed (API keys, user credentials, etc.) should be kept out of client-side code as much as possible.

Real-World Examples and Statistics

To drive home the importance of securing API keys, let‘s look at some real-world examples and statistics:

  • In 2020, security researchers found over 25,000 Google API keys exposed publicly through Android apps. Many of these keys granted access to sensitive user data in Google services like Gmail and Drive. (Source)
  • In 2019, an attacker compromised API keys for the cryptocurrency exchange Binance, allowing them to steal over $40 million worth of Bitcoin. (Source)
  • According to a recent report by Akamai, API attacks increased by 168% in 2021, with credential stuffing and SQL injection being the most common attack vectors. (Source)
  • The serverless computing market is expected to grow from $7.6 billion in 2020 to $21.1 billion by 2025, driven in part by the adoption of serverless functions for security-critical use cases. (Source)

These examples highlight the real risk of exposed API keys and the growing importance of serverless security solutions like Netlify Functions.

Conclusion

Securing API keys is a critical task for any React developer working with third-party services. Exposing keys in client-side code is a major security risk that can lead to data breaches, financial losses, and reputational damage.

Netlify Functions provide a simple, serverless solution for securely accessing API keys from a React frontend. By moving sensitive data to server-side functions, you can ensure that keys are never exposed to users, while still allowing your frontend to make authenticated requests to external services.

In this guide, we‘ve looked at:

  • The risks of including API keys in client-side code
  • How Netlify Functions keep keys secure by executing on the server
  • Example code for making authenticated requests to Stripe from a Netlify Function
  • Architecture diagrams for using Netlify Functions as a secure proxy
  • Best practices like secret management with environment variables and on-demand function deploys
  • Real-world examples and statistics that highlight the importance of API security

Remember, Netlify Functions are just one tool in the API security toolbox. As a frontend developer, your goal should be to minimize the sensitive data exposed to clients as much as possible. By combining serverless functions with other security best practices like Oauth, encryption, and rate limiting, you can build React apps that are both feature-rich and secure.

Similar Posts