RoadToChain Logo
RoadToChain
T2/M2.3/The day I realized the frontend is not enough
beginner 13m read

The day I realized the frontend is not enough

Why frontend code is a public document, how API key exposure happens in production, and the four operations that require a backend.

#security #backend #api-keys #frontend-limits #mistake

It was a Tuesday night. I was testing ChainElect's voter registration — profile photo upload working perfectly, Supabase saving user data, MetaMask wallet binding all smooth.

Then I got a DM from a developer friend I'd sent the link to:

"Hey, nice project. Also... is this your Pinata API key? Found it in the JS bundle. pk_...5f8a. You might want to rotate that."

I opened Chrome DevTools. Went to the Sources tab. Searched "pinata".

There it was. Baked directly into the compiled JavaScript. My production Pinata API key. Readable by anyone. No hacking required. Just Ctrl+F.

Within 20 minutes, I had rotated the key, revoked the old one, and started planning a backend.


1. The Root Cause: What Happens to Frontend Code

When you run npm run build, Vite (or Webpack) takes all your JavaScript source files and bundles them into a single compressed file that gets served to every browser that visits your site.

Here is the critical truth about frontend code: it is a public document.

Every line of JavaScript your browser runs was downloaded from a server. Anyone can:

  • Open DevTools → Sources → read it
  • Run strings on the minified bundle
  • Use a beautifier to restore readable code
  • Intercept network requests to see API calls in plain text

This is not a bug. It is the fundamental architecture of the web. Browsers need the code to run it.

SmartAccount.sol
Your code (source):
  const apiKey = import.meta.env.VITE_PINATA_KEY;

After build + minify (served to users):
  const a="pk_eyJhbGciOiJIUzI1NiJ9...actualKeyValue..."

The VITE_ prefix means "include this in the bundle." Minification renames the variable. The value stays.


2. The Visual: What Gets Exposed

SmartAccount.sol
YOU THINK:
  .env file ──► VITE_PINATA_KEY=sk_prod_... (secret, on your machine)

WHAT HAPPENS:
  Vite build ──► inlines the value into bundle.js
  bundle.js ──► served to every visitor's browser
  DevTools   ──► anyone can find "sk_prod_..."

THE SECURE VERSION:
  Frontend ──► POST /api/upload (no secrets sent)
                        │
               Express backend (runs on server)
               Reads PINATA_KEY from server .env
               (server .env is NEVER sent to browsers)
                        │
               Calls Pinata API with key
                        │
               Returns CID to frontend

[!TIP] VISUAL TRIGGER FOR FRONTEND: Animate the insecure flow as a chain where the API key visibly "travels" from the env file → bundle → browser → network request. Then show the secure flow where the key stays locked inside a server vault icon that only the backend can access. The frontend only ever receives the output (CID), never the secret.

Frontend vs Backend: Secret Key Boundaries
Any variable bundled in client-side JavaScript is public. Secret API keys must reside behind a server-side boundary and only be accessed via proxy endpoints.

3. Technical Explanation: What the Frontend Cannot Do

There are four operations that require a backend in any serious Web3 dApp:

1. API Secret Management Pinata, Alchemy paid keys, Supabase service role keys, email service API keys — anything that has financial cost or security implications must live on the server.

2. Server-Side Authentication MetaMask proves wallet ownership but cannot prove the user's email, their registration status, or their admin role in a traditional auth system. ChainElect uses Supabase sessions, managed through the Express backend:

index.js
javascript
// context/ChainElectBackend/server.js (auth middleware)
const isAuthenticated = (req, res, next) => {
  if (!req.session.userId) {
    return res.status(401).json({ error: 'Please log in first' });
  }
  next();
};

3. Rate Limiting Without a backend, a bot can call your Pinata-powered upload endpoint 10,000 times per minute. With Express + rate-limit middleware, you control access.

4. Sensitive Computations Anything that would expose business logic, pricing formulas, or internal data structures should not run in browser JavaScript where it can be inspected.


// Reality Check

Adding a backend does NOT undermine decentralization. The blockchain remains the trust layer — ownership, votes, and proofs are still fully on-chain and verifiable by anyone. The backend is the access control layer that manages authentication and API secrets. If the backend goes offline, the on-chain state remains intact and accessible through any RPC node.

— Production Engineering Principle

// I Got This Wrong

The Next.js False Security Pattern: Many developers think that using Next.js API routes makes their secrets safe because the code is in /api files. This is true only if those secrets are never imported into client-side components. If you import an API route's module from a page.tsx, Next.js may accidentally bundle the server-side code into the client bundle. Always use server-only package imports or ensure strict separation of client and server code.

— Postmortem Confession

System Design Challenge
Think Active

Open any production dApp's compiled JavaScript bundle (e.g. load the site → DevTools → Sources → find the main.js bundle). Use Ctrl+F to search for common API key patterns: pk_, sk_, Bearer, apiKey, token. What do you find? This exercise reveals exactly how common the API key exposure mistake is in production Web3 apps.

[ Think Before Continuing ]

Was this lesson helpful?

Let us know what you think of this specification. (submitting anonymously)