Back to Blog

Protecting API keys in frontend apps with Netlify Functions

Build a secure Netlify serverless proxy to hide API keys from client-side code. Includes cross-domain setup and CORS handling.

By Marios Sofokleous
Published ·Updated
Illustration showing API key security with serverless functions protecting React applications from unauthorized access

The problem: API keys in client-side code

I've always been aware of a fundamental security flaw: API keys in client-side code are visible to everyone. Anyone can view your source code or inspect network requests to extract these sensitive credentials.

This creates serious security risks including unauthorized API usage and quota exhaustion. The challenge wasn't discovering this problem but finding a practical solution to mitigate it.

The solution: Serverless functions as API proxies

Since my learning journey focuses primarily on frontend development, I wanted a solution that wouldn't require tedious backend configuration and deployment complexity.

Serverless functions emerged as the perfect alternative. They provide secure proxy functionality without server management.

With serverless, I could focus on what I love most: building frontend applications while still implementing proper security practices.

What we'll build: A simple weather information app

In this tutorial, I'll demonstrate how to get started with Netlify Functions to protect API keys while building a simple weather information page that fetches current weather conditions for a specific city.

Our architecture:

We'll create two separate components that work together across different domains:

  1. Proxy service: A dedicated Netlify project containing only the serverless function (no frontend) that stores your API key and makes authenticated requests to AccuWeather
  2. Frontend: A simple HTML page (index.html) that calls your serverless function and displays the weather data

How it works:

  • The frontend sends a POST request with a city name to the serverless function
  • The function securely calls AccuWeather's API using your protected API key
  • Weather data flows back through your function to the frontend for display
  • Your API key never appears in client-side code or browser requests

This separation demonstrates a key advantage of serverless functions: one secure API proxy can serve multiple frontend applications across different domains and hosting platforms.

Getting started

Now let's dive into the implementation details, starting with setting up your development environment for your proxy service and creating your first serverless function.

Step 1: Create a new repository from the Netlify template

Use the official Netlify template to create a new repository that includes all the necessary boilerplate:

Template URL: https://github.com/netlify/explorers-up-and-running-with-serverless-functions

Click "Use this template" to create your own repository, then clone it to your local machine using VS Code or your preferred method.

Step 2: Initialize your npm project

Before setting up Netlify, initialize your project as an npm project:

npm init

You can press Enter to accept the default values for all prompts, or customize them as needed.

Step 3: Install the Netlify CLI

Install the Netlify CLI globally to manage your functions and deployments:

npm install netlify-cli --global

Step 4: Set up your Netlify account and project

  1. Create a Netlify account if you haven't already at netlify.com
  2. Login to Netlify from your terminal:
    ntl login
    
  3. Initialize your project with Netlify:
    ntl init
    
    When prompted, configure these settings:
    • Directory to deploy: public
    • Create netlify.toml? Yes

Step 5: Configure for modern JavaScript (ESM)

Since we'll be using modern JavaScript syntax, add ESM support and configure the build process:

  1. Add to your package.json:
    { "type": "module" }
    
  2. Update your netlify.toml file:
    [functions]
        node_bundler = "esbuild"
    

Step 6: Get your AccuWeather API key

  1. Sign up for a free account at developer.accuweather.com
  2. Create a new app to get your API key
  3. Add your API key to Netlify:
    • Go to your site dashboard on Netlify
    • Navigate to Project configuration > Environment variables
    • Click Add a single variable
    • Set the Key as ACCUWEATHER_API_KEY
    • Check the Contains secret values checkbox (since API keys are sensitive)
    • Under the Different value for each deploy context, configure the following:
      • Production: Your live API key
      • Local development (Netlify CLI): Same API key or a separate development key if you have one
    • Click Save variable

Step 7: Create your serverless function

Create the file netlify/functions/weather.js with the following code:

const ACCUWEATHER_ENDPOINTS = {
  CITY_SEARCH: 'http://dataservice.accuweather.com/locations/v1/cities/search',
  CURRENT_CONDITIONS: 'http://dataservice.accuweather.com/currentconditions/v1'
};

const ACCUWEATHER_API_KEY = process.env.ACCUWEATHER_API_KEY;

export const handler = async (event) => {
  // CORS headers for cross-domain requests
  const headers = {
    "Content-Type": "application/json",
    "Access-Control-Allow-Origin": "*", // For development - restrict in production
    "Access-Control-Allow-Headers": "Content-Type",
    "Access-Control-Allow-Methods": "POST, OPTIONS"
  };

  // Handle OPTIONS preflight request (required for CORS)
  if (event.httpMethod === "OPTIONS") {
    return {
      statusCode: 200,
      headers,
      body: ""
    };
  }

  // Handle POST request
  if (event.httpMethod === "POST") {
    try {
      const { city } = JSON.parse(event.body);

      // Get location key
      const locationResponse = await fetch(`${ACCUWEATHER_ENDPOINTS.CITY_SEARCH}?apikey=${ACCUWEATHER_API_KEY}&q=${encodeURIComponent(city)}`);
      const locationData = await locationResponse.json();
      const { Key: locationKey } = locationData[0];

      // Get current conditions
      const weatherResponse = await fetch(`${ACCUWEATHER_ENDPOINTS.CURRENT_CONDITIONS}/${locationKey}?apikey=${ACCUWEATHER_API_KEY}`);
      const weatherData = await weatherResponse.json();
      const { WeatherText: weatherText } = weatherData[0];

      return {
        statusCode: 200,
        headers,
        body: JSON.stringify({
          message: `The current weather in ${city} is: ${weatherText}`
        }),
      };
    } catch (error) {
      return {
        statusCode: 500,
        headers,
        body: JSON.stringify({
          error: "Failed to fetch weather data"
        })
      };
    }
  }

  // Handle unsupported methods
  return {
    statusCode: 405,
    headers,
    body: JSON.stringify({ error: "Method Not Allowed" })
  };
}

Key points about this function:

  • The API key is stored securely in process.env.ACCUWEATHER_API_KEY
  • The function handles two API calls: first to get the location key, then to get weather data
  • It returns a simple message with the weather information

Key additions for cross-domain support:

  • CORS headers: Allow requests from any origin (*) for development
  • OPTIONS handler: Responds to browser preflight requests
  • Method validation: Ensures only POST and OPTIONS are accepted

For production, replace "Access-Control-Allow-Origin": "*" with your specific frontend domain:

"Access-Control-Allow-Origin": "https://yourfrontend.com"

Step 8: Deploy your proxy service

Let's deploy the proxy service to production:

  1. Add all your changes to git:
    git add .
    
  2. Commit your changes with a descriptive message:
    git commit -m "feat: add weather proxy function with CORS support"
    
  3. Push to your main branch:
    git push origin main
    

This will automatically trigger a build inside Netlify since your repository is connected to your Netlify site.

Important reminder: Make sure your ACCUWEATHER_API_KEY environment variable is properly set in your Netlify site settings before deployment (as covered in Step 6), as the serverless function won't work without it.

Step 9: Create your frontend

Now we'll create a simple HTML page separate from your proxy service that will call your deployed Netlify function. This frontend can be hosted anywhere (local file, CodeSandbox, different hosting provider, etc.).

Create an index.html file with the following code:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Weather App - Secure API Proxy Demo</title>
  </head>
  <body>
    <h1>Weather App - Secure API Proxy Demo</h1>
    <button id="fetch-btn">Fetch Weather For Tokyo</button>
    <p id="response-output">Response Example Placeholder</p>
    <script>
      // Replace 'YOUR_NETLIFY_SITE' with your actual Netlify site name
      const NETLIFY_FUNCTION_URL = 'https://YOUR_NETLIFY_SITE.netlify.app/.netlify/functions/weather';

      document.addEventListener('DOMContentLoaded', () => {
        const fetchBtn = document.getElementById('fetch-btn')
        const responseText = document.getElementById('response-output')

        fetchBtn.addEventListener('click', async () => {
          const response = await fetch(`${NETLIFY_FUNCTION_URL}`, {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({ city: "Tokyo" }),
          });
          const data = await response.json();
          responseText.innerText = data.message || 'No message received';
        });
      })
    </script>
  </body>
</html>

Important: Replace YOUR_NETLIFY_SITE in the NETLIFY_FUNCTION_URL with your actual Netlify site name.

Step 10: Test your cross-domain setup

  1. Open your frontend: Open index.html in your browser
  2. Test the connection: Click the "Fetch Weather For Tokyo" button
  3. Verify the results: You should see the placeholder text "Response Example Placeholder" replaced with actual weather information like: "The current weather in Tokyo is: Sunny"

Step 11: Optional security enhancement (rate limiting)

While hiding your API key is the primary goal of this tutorial, you can add another layer of protection by enabling Netlify's built-in rate limiting. This will protect your serverless functions from abuse.

You can configure rate limiting directly in your netlify.toml file:

[[rules]]
  for = "/.netlify/functions/*"
  [rules.rate_limit]
    rate = "20 requests per minute"
    action = "block"

What we accomplished

This setup demonstrates the core security principle: your AccuWeather API key is completely hidden from client-side code. When users inspect your website's source code or network requests, they'll only see calls to your serverless function, not to the AccuWeather API directly.

Key benefits achieved:

  • API key protection: Sensitive credentials remain secure on Netlify's servers
  • Cross-domain flexibility: Your proxy can serve multiple frontend applications across different hosting platforms
  • Simple architecture: No complex backend infrastructure required

The serverless function acts as a secure proxy, making the actual API calls with your protected credentials and returning only the data you want to share with the frontend.


Share this page
Back to Blog

Let's connect

Interested in adding me to your development team?