npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

secure-api-payload

v1.0.1

Published

Frontend API payload encryption using RSA-OAEP and AES-256-GCM

Readme

Secure API Payload

A lightweight browser-side JavaScript package for encrypting API request payloads before sending them to your backend.

Secure API Payload uses AES-256-GCM to encrypt the payload and RSA-OAEP with SHA-256 to encrypt the AES key.

It supports JSON payloads, FormData, and file uploads.

Note: This package provides an additional application-level encryption layer. Always use HTTPS in production.

Features

  • AES-256-GCM payload encryption
  • RSA-OAEP key encryption
  • SHA-256
  • JSON payload support
  • FormData support
  • File upload support
  • Custom RSA public keys
  • Browser Web Crypto API
  • React support
  • Vite support
  • Next.js support
  • Vue support
  • Vanilla JavaScript support

Installation

npm install secure-api-payload

RSA Key Generation

Secure API Payload uses an RSA key pair:

Private Key
|
| Used by Backend
|
v
Decrypt AES Key

Public Key
|
| Used by Frontend
|
v
Encrypt AES Key

The public key can be used in the frontend.

The private key must only be stored on the backend.

Generate Private Key

Install OpenSSL if it is not already installed.

Generate a 4096-bit RSA private key:

openssl genrsa -out private.pem 4096

This creates:

private.pem

Example:

-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----

Generate Public Key

Generate the public key from the private key:

openssl rsa \
-in private.pem \
-pubout \
-out public.pem

This creates:

public.pem

Example:

-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----

You now have:

private.pem
public.pem

Key Usage

Use the keys as follows:

Frontend
|
+---- public.pem
|
v
Encrypt AES Key

Backend
|
+---- private.pem
|
v
Decrypt AES Key

Important

Never:

  • Put private.pem in the frontend
  • Put private.pem in the public folder
  • Put private.pem in src
  • Commit private.pem to GitHub
  • Store private.pem in localStorage
  • Store private.pem in sessionStorage
  • Send private.pem to the browser

Add the following to .gitignore:

\*.pem
\*.key

Usage

Configure Your Own Public Key

The package allows you to use your own RSA public key.

First generate your keys:

openssl genrsa -out private.pem 4096

openssl rsa \
-in private.pem \
-pubout \
-out public.pem

Open:

public.pem

You will see something similar to:

-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
YOUR_PUBLIC_KEY_CONTENT
YOUR_PUBLIC_KEY_CONTENT
...
-----END PUBLIC KEY-----

Copy the complete public key, including:

-----BEGIN PUBLIC KEY-----

and:

-----END PUBLIC KEY-----

Then replace:

const PUBLIC_KEY = `
-----BEGIN PUBLIC KEY-----
YOUR_PUBLIC_KEY
-----END PUBLIC KEY-----
`;

with your actual public key:

const PUBLIC_KEY = `
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
YOUR_ACTUAL_PUBLIC_KEY
YOUR_ACTUAL_PUBLIC_KEY
...
-----END PUBLIC KEY-----
`;

Then configure the package:

import { configureSecureAPI, APICallPayload } from 'secure-api-payload';

configureSecureAPI({
  publicKey: PUBLIC_KEY,
});

Using a Public Key from an Environment Variable

For Vite:

VITE_RSA_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
YOUR_PUBLIC_KEY
-----END PUBLIC KEY-----"

Then:

import { configureSecureAPI } from 'secure-api-payload';

const PUBLIC_KEY = import.meta.env.VITE_RSA_PUBLIC_KEY;

configureSecureAPI({
  publicKey: PUBLIC_KEY,
});

A public key is not a secret. It is safe to expose in frontend code or frontend environment variables.

JSON Payload

const payload = {
  name: 'Anoop Tripathi',
  email: '[email protected]',
  phone: '9999999999',
};

const response = await APICallPayload('https://api.example.com/users', payload);

const data = await response.json();

console.log(data);

The original JSON payload is encrypted before it is sent to the API.

POST Request

const response = await APICallPayload(
  'https://api.example.com/users',
  {
    name: 'Anoop Tripathi',
    email: '[email protected]',
  },
  {
    method: 'POST',
  }
);

const data = await response.json();

PUT Request

const response = await APICallPayload(
  'https://api.example.com/users/123',
  {
    name: 'Updated Name',
    email: '[email protected]',
  },
  {
    method: 'PUT',
  }
);

const data = await response.json();

PATCH Request

const response = await APICallPayload(
  'https://api.example.com/users/123',
  {
    email: '[email protected]',
  },
  {
    method: 'PATCH',
  }
);

const data = await response.json();

DELETE Request

const response = await APICallPayload(
  'https://api.example.com/users/123',
  {
    reason: 'Account deletion',
  },
  {
    method: 'DELETE',
  }
);

const data = await response.json();

Custom Headers

const response = await APICallPayload(
  'https://api.example.com/users',
  {
    name: 'Anoop',
  },
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'X-App-Version': '1.0.0',
    },
  }
);

const data = await response.json();

FormData

const formData = new FormData();

formData.append('name', 'Anoop Tripathi');

formData.append('email', '[email protected]');

const response = await APICallPayload(
  'https://api.example.com/users',
  formData
);

const data = await response.json();

console.log(data);

File Upload

const formData = new FormData();

formData.append('name', 'Anoop Tripathi');

formData.append('document', file);

const response = await APICallPayload(
  'https://api.example.com/upload',
  formData
);

const data = await response.json();

console.log(data);

Authentication

Encryption does not replace authentication.

You can continue using your existing authentication mechanism.

const response = await APICallPayload(
  'https://api.example.com/users',
  {
    name: 'Anoop',
  },
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  }
);

The backend should independently validate the authentication token.

How It Works

Frontend
|
| JSON / FormData
v
Secure API Payload
|
| Generate random AES-256 key
|
| Encrypt payload using AES-256-GCM
|
| Encrypt AES key using RSA-OAEP
v
Encrypted Payload
|
| HTTPS
v
Backend
|
| RSA Private Key
|
| Decrypt AES Key
|
| Decrypt Payload
v
Original Payload

Encryption

| Purpose | Algorithm | | ------------------ | -------------- | | Payload encryption | AES-256-GCM | | AES key encryption | RSA-OAEP | | RSA hash | SHA-256 | | Cryptography API | Web Crypto API |

Backend Decryption

The backend must have the RSA private key corresponding to the public key used by the frontend. The private key must never be exposed to the frontend or committed to Git.

The backend uses Node.js built-in crypto and fs modules to decrypt the AES key and payload.

Decryption Code

import crypto from 'crypto';
import fs from 'fs';

const privateKey = fs.readFileSync('./keys/private.pem', 'utf8');

function decryptPayload(payload) {
  const encryptedKey = Buffer.from(payload.key, 'base64');

  const aesKey = crypto.privateDecrypt(
    {
      key: privateKey,
      padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
      oaepHash: 'sha256',
    },
    encryptedKey
  );

  const iv = Buffer.from(payload.iv, 'base64');
  const encrypted = Buffer.from(payload.data, 'base64');

  const authTagLength = 16;

  const authTag = encrypted.subarray(encrypted.length - authTagLength);

  const ciphertext = encrypted.subarray(0, encrypted.length - authTagLength);

  const decipher = crypto.createDecipheriv('aes-256-gcm', aesKey, iv);

  decipher.setAuthTag(authTag);

  const decrypted = Buffer.concat([
    decipher.update(ciphertext),
    decipher.final(),
  ]);

  return JSON.parse(decrypted.toString('utf8'));
}

Express Example

import express from 'express';
import { decryptPayload } from './decryptPayload.js';

const app = express();

app.use(express.json({ limit: '20mb' }));

app.post('/api/test', (req, res) => {
  try {
    const payload = decryptPayload(req.body);

    console.log('Decrypted payload:', payload);

    res.json({
      success: true,
      payload,
    });
  } catch (error) {
    console.error('Payload decryption failed:', error.message);

    res.status(400).json({
      success: false,
      message: 'Invalid encrypted payload',
    });
  }
});

app.listen(8080, () => {
  console.log('Backend running on http://localhost:8080');
});

Backend Key Location

backend/
├── keys/
│   └── private.pem
├── decryptPayload.js
├── server.js
└── package.json

Add the following to the backend .gitignore:

*.pem
*.key
keys/
.env
.env.*

Decryption Flow

Encrypted Request
       |
       +---- key
       |      |
       |      +--> RSA-OAEP + Private Key
       |                    |
       |                    v
       |               AES-256 Key
       |
       +---- iv
       |
       +---- data
              |
              +--> AES-256-GCM + AES Key + IV
                              |
                              v
                       Original Payload

The decrypted result will match the normalized payload generated by the frontend. For example, a JSON request returns a structure such as:

{
  type: 'json',
  data: {
    name: 'Anoop',
    email: '[email protected]',
  },
}

For FormData, the backend receives the normalized structure containing type: 'form-data' and its fields.

Important: Always use HTTPS in production. Frontend payload encryption does not replace TLS, authentication, authorization, input validation, rate limiting, or other backend security controls.

Browser Network Request

Without encryption, the browser Network tab may show:

{
  "name": "Anoop Tripathi",
  "email": "[email protected]"
}

With Secure API Payload, the application payload is encrypted before being sent.

The request body will contain encrypted values similar to:

{
  "key": "encrypted-aes-key",
  "iv": "random-iv",
  "data": "encrypted-payload"
}

HTTPS is still required. This package does not replace TLS.

Security

This package does not replace:

  • HTTPS/TLS
  • Authentication
  • Authorization
  • Input validation
  • Rate limiting
  • CSRF protection
  • Backend security controls

Always validate and authorize requests on the backend.

Important Security Limitation

Frontend encryption does not make the frontend trusted.

A user controlling the browser can potentially:

  • Inspect JavaScript
  • Debug JavaScript
  • Modify JavaScript
  • Read data before encryption
  • Hook encryption functions
  • Construct custom API requests

Therefore, never rely on frontend encryption alone for:

  • Authentication
  • Authorization
  • Permissions
  • Pricing
  • Business logic
  • Fraud prevention

All important security controls must be implemented on the backend.

Project Structure

secure-api-payload/
│
├── .husky/
│ └── pre-commit
│
├── .vscode/
│ └── settings.json
│
├── src/
│ ├── api/
│ │ └── APICallPayload.js
│ │
│ ├── config/
│ │ └── config.js
│ │
│ ├── crypto/
│ │ ├── aes.js
│ │ ├── base64.js
│ │ ├── encrypt.js
│ │ ├── nonce.js
│ │ └── rsa.js
│ │
│ ├── payload/
│ │ └── normalize.js
│ │
│ └── index.js
│
├── .gitignore
├── .prettierignore
├── .prettierrc
├── package.json
├── package-lock.json
├── vite.config.js
└── README.md

License

MIT