How to securely store API keys in JavaScript apps?

Content verified by Anycode AI
July 21, 2024
Security of API keys is one of the most vital challenges a developer would encounter when developing a JavaScript application. Essentially, while the reliance on third-party services or APIs increases, protecting sensitive credentials is point one in the installation. Your API key exposure to client-side code opens you up for unauthorized access, potential data breaches, and financial losses. This guide addresses the common API key management pitfalls in JavaScript apps and gives practical solutions that will help to safe-guard your keys. We will see best practices for balancing it between security and functionality, from server-side proxying to environment variables. It can help developers significantly minimize the risks of API key exposure without losing either the performance or the usability of their apps. Whether building a small project or a large-scale application, these techniques will help ensure your API keys remain secure and your services protected.

API keys are super important for your app to communicate securely with third-party services. But wow, keeping them safe—especially in JavaScript apps that run in the browser—is no small task. Here’s a little guide to help you out:

Skip Storing API Keys on the Client

First thing first, DO NOT store API keys on the client side. Seriously! Client-side JS can be peeked at, tinkered with, and generally messed around by anyone who knows how to open a browser console.

Use Environment Variables on the Server Side

Instead of embedding API keys directly in your code (eek!), use environment variables. Here’s how you can do it with Node.js:

Create a .env File: Pop this file in your project's root directory.

touch .env

Add Your API Key:

API_KEY=your_api_key_here

Install dotenv: This nifty package helps load the stuff from your .env file.

npm install dotenv

Load Environment Variables in Code:

require('dotenv').config();

const express = require('express');
const app = express();
const apiKey = process.env.API_KEY;

app.get('/api/some-endpoint', (req, res) => {
    const axios = require('axios');

    axios.get('https://api.example.com/data', {
        headers: {
            'Authorization': `Bearer ${apiKey}`
        }
    })
    .then(response => {
        res.json(response.data);
    })
    .catch(error => {
        res.status(500).send('Error fetching data');
    });
});

app.listen(3000, () => {
    console.log('Server running on port 3000');
});

Proxy API Requests Through Your Server

Let your server be the middleman. The client pings your server -> the server uses the API key -> third-party service gets the request. This keeps your API key hidden, tucked away on the server where it’s safe.

// client.js

fetch('http://localhost:3000/api/some-endpoint')
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error('Error fetching data:', error));

Secure Your Server

Your server is the gatekeeper. Here’s how to fortify it:

  • Use HTTPS: Encrypt data during transfer. Keeps the bad guys out.
  • Authenticate Requests: Use JWTs (JSON Web Tokens) to verify who's knocking on your server's door.
  • Rate Limiting: Don't let anyone flood your server. Limit the number of requests they can make.
const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
    windowMs: 15 * 60 * 1000, 
    max: 100, 
    message: 'Too many requests, try again later'
});

app.use('/api/', limiter);

Use Environment Variables in Front-end Frameworks

Avoid storing API keys in front-end .env files. Use them for less sensitive stuff, like base URLs.

React Example:

Create a .env file in your React project’s root.

REACT_APP_API_BASE_URL=https://yourproxyserver.com

Access it in your React component.

import React, { useEffect, useState } from 'react';

const App = () => {
    const [data, setData] = useState(null);

    useEffect(() => {
        fetch(`${process.env.REACT_APP_API_BASE_URL}/api/some-endpoint`)
            .then(response => response.json())
            .then(data => setData(data))
            .catch(error => console.error('Error fetching data:', error));
    }, []);

    return (
        <div>
            <h1>Data</h1>
            {data ? <pre>{JSON.stringify(data, null, 2)}</pre> : 'Loading...'}
        </div>
    );
};

export default App;

Use Secrets Management Services

For the high level secret-keeping, try services like AWS Secrets Manager, Google Cloud Secret Manager, or HashiCorp Vault.

AWS Secrets Manager Example:

npm install aws-sdk

const AWS = require('aws-sdk');
const express = require('express');
const app = express();

AWS.config.update({ region: 'us-west-2' });
const secretsManager = new AWS.SecretsManager();

app.get('/api/some-endpoint', async (req, res) => {
    try {
        const data = await secretsManager.getSecretValue({ SecretId: 'YourSecretId' }).promise();
        const apiKey = JSON.parse(data.SecretString).API_KEY;

        const axios = require('axios');
        const response = await axios.get('https://api.example.com/data', {
            headers: {
                'Authorization': `Bearer ${apiKey}`
            }
        });
        res.json(response.data);
    } catch (error) {
        console.error('Error fetching secret:', error);
        res.status(500).send('Error fetching data');
    }
});

app.listen(3000, () => {
    console.log('Server running on port 3000');
});

By following these strategies, you can keep your API keys secure and ensure your application is safe from unauthorized access. Stay safe out there!

Have any questions?
Our CEO and CTO are happy to
answer them personally.
Get Beta Access
Anubis Watal
CTO at Anycode
Alex Hudym
CEO at Anycode