DEV Community

Cover image for Front-end Signing and Back-end Verification in JavaScript Applications
Kyrylo Sotnykov
Kyrylo Sotnykov

Posted on • Originally published at Medium

Front-end Signing and Back-end Verification in JavaScript Applications

We live in a time of offline apps, AI-generated content, and decentralized storage. In this world, guaranteeing data authenticity is no longer optional. Here’s how you can sign data on the front-end and verify on the back-end — simply and securely.

Why Data Integrity Matters
Whether you’re syncing user-generated data, caching files locally, or fetching data from unstrusted APIs or IPFS — you’d like to have a way to tell if the data is still valid and unchanged.

Some real-world use cases:

  • Digitally signed documents or contracts
  • Secure exports/imports between systems
  • App configurations stored locally
  • Offline apps that sync later

What We Want to Achieve

  1. The front-end signs the data with a private key (in-memory or user secret derived)
  2. The back-end verifies the signature with public key (safely stored in .env or config)
  3. Optionally, we validate the shape of the data with JSON Schema

How to do it in JS/TS?
You can use sign-proof — a lightweight Typescript library build on Ed25519 cryptography with JSON Schema support.

npm install sign-proof
Enter fullscreen mode Exit fullscreen mode

Key Storage & Generation

You need to generate Ed25519 key pair only once per user or session. This is how to handle keys securely:

import { generateKeyPair } from 'sign-proof';

const { publicKey, privateKey } = generateKeyPair();
Enter fullscreen mode Exit fullscreen mode
  • Private key: This should be kept secret in the browser, for example by using encrypted localStorage or a session.
  • Public key: Can be securely shared with back-end for signature verification

Example: Make the key pair on first login or account registration in the browser and send the publicKey to the back-end to associate it with the user. Store the privateKey securely on the client (e.g. memory, IndexedDB, secure storage)

Front-end: Signing the Data

import { signData} from 'sign-proof';

const privateKey = '...'; // base64

// Example user input
const payload = { message: 'Hello world!', userId: 5 };

// Sign it
const proof = signData(payload, privateKey);

// Send to backend
const signed = { payload, proof};
fetch('/api/messages/send', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(signed),
});
Enter fullscreen mode Exit fullscreen mode

Back-end: Verifying the Data

import { verifyData} from 'sign-proof';

app.post('/api/message/send', (req, res) => {
  const { payload, proof}: SignedData<any> = req.body;

  // Get public key from database
  const publicKey = database.getPublicKey(payload.userId);

  // Verify proof
  const isValid = verifyData({ payload, proof }, publicKey);

  if (!isValid) return res.status(400).send({ ok: false, reason: 'Invalid signature' });

  // Proceed with trusted data
  res.send({ ok: true });
});
Enter fullscreen mode Exit fullscreen mode

Why Ed25519?

  • Lightweight, speedy elliptic-curve algorithm
  • No heavy libraries or key infrastructure
  • Extremely small footprint — front-end-friendly
  • Used by Signal, WhatsApp, and blockchain systems

Summary
It this workflow:

  • Signing is performed on the front-end using the private key
  • Verification is performed on the back-end using only the public key
  • By separating the concerns between verify and sign, you have a secure, scalable, and tamper-proof data-validation process — across environments and devices.

Why Not Try It Yourself
NPM Package
GitHub Repo

Give it a ⭐ if you find it helpful!

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.