# Topic selected: Option A – Purely Technical: "Building a Secure AI Proxy for Browser Tools

> Source: <https://dev.to/obed_avorlenu/topic-selected-option-a-purely-technical-building-a-secure-ai-proxy-for-browser-tools-5bc>
> Published: 2026-08-09 18:20:32+00:00

This is the strongest choice. It teaches a tangible, highly demanded skill (API key security) with actual code, making the backlink to AfriWidget feel like a natural, neutral citation rather than a sales pitch.

Here is the article, rewritten to be strictly technical, objective, and genuinely useful for dev.to readers.

Stop Exposing Your AI API Keys: Build a Secure Proxy with Cloudflare Workers

We have all seen it. You open the browser's DevTools on a "cutting-edge" AI startup's landing page, check the Network tab, and find a direct POST request to api.openai.com containing a plaintext API key in the headers.

It is one of the most common—and dangerous—mistakes in modern web development. Exposing your LLM API key client-side is an open invitation for abuse, leading to stolen credits, hefty bills, and potential account suspension.

The standard solution is the Backend-for-Frontend (BFF) proxy pattern. But how do you implement it practically, cheaply, and securely without spinning up a heavy Express server?

In this guide, I will walk you through building a lightweight, serverless AI proxy using Cloudflare Workers to securely call Groq (or OpenAI) APIs from your browser-based calculators and tools.

The Architecture: How It Works

Instead of your frontend talking directly to the AI provider, we introduce a stateless middleware layer:

```
Browser App → Cloudflare Worker (Proxy) → Groq/OpenAI API
     ↑                ↑
  (No API Key)   (API Key stored securely in Worker env vars)
```

The Worker's responsibilities:

Step 1: Scaffolding the Cloudflare Worker

We will use the new create-cloudflare CLI. Make sure you have Node.js installed.

```
npm create cloudflare@latest ai-proxy
```

Choose "Hello World" worker and TypeScript. Once inside the directory, install the Groq SDK:

```
npm install groq-sdk
```

Step 2: Securing the API Key

Never hardcode keys. Cloudflare Workers expose environment variables securely.

Update your wrangler.toml:

```
name = "ai-proxy"
main = "src/index.ts"
compatibility_date = "2024-12-18"

[vars]
GROQ_API_KEY = "your-secret-key-here" # Replace, but prefer using wrangler secret for production
```

For production, set it as an actual secret to hide it from the dashboard:

```
npx wrangler secret put GROQ_API_KEY
```

Step 3: Writing the Worker Logic

We need an endpoint that accepts POST requests, validates the input, calls Groq, and returns the result.

Here is the full src/index.ts implementation:

``` js
import { Groq } from "groq-sdk";

export interface Env {
  GROQ_API_KEY: string;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // 1. CORS Preflight (handle OPTIONS)
    if (request.method === "OPTIONS") {
      return new Response(null, {
        headers: {
          "Access-Control-Allow-Origin": "*",
          "Access-Control-Allow-Methods": "POST, OPTIONS",
          "Access-Control-Allow-Headers": "Content-Type",
        },
      });
    }

    // 2. Only allow POST requests
    if (request.method !== "POST") {
      return new Response(JSON.stringify({ error: "Method not allowed" }), {
        status: 405,
        headers: { "Content-Type": "application/json" },
      });
    }

    try {
      // 3. Parse and sanitize input
      const body = await request.json();
      const { context, promptType } = body;

      if (!context || typeof context !== "string") {
        return new Response(
          JSON.stringify({ error: "Missing 'context' field" }),
          { status: 400, headers: { "Content-Type": "application/json" } }
        );
      }

      // 4. Initialize Groq with the secret key from environment
      const groq = new Groq({
        apiKey: env.GROQ_API_KEY,
      });

      // 5. Construct a system prompt based on the type (e.g., finance, health)
      let systemPrompt = "You are a helpful financial assistant.";
      if (promptType === "health") {
        systemPrompt =
          "You are a medical disclaimer assistant. Provide general wellness info only, no diagnoses.";
      }

      // 6. Call the LLM
      const chatCompletion = await groq.chat.completions.create({
        messages: [
          { role: "system", content: systemPrompt },
          {
            role: "user",
            content: `Explain what these calculations mean in plain English: ${context}`,
          },
        ],
        model: "llama3-8b-8192", // Fast and cheap Groq model
        temperature: 0.5,
        max_tokens: 200,
      });

      const reply = chatCompletion.choices[0]?.message?.content || "No response generated.";

      // 7. Return the response to the browser with CORS headers
      return new Response(
        JSON.stringify({ success: true, data: reply }),
        {
          headers: {
            "Content-Type": "application/json",
            "Access-Control-Allow-Origin": "*",
          },
        }
      );
    } catch (error) {
      console.error("Proxy error:", error);
      return new Response(
        JSON.stringify({ error: "Internal server error" }),
        { status: 500, headers: { "Content-Type": "application/json" } }
      );
    }
  },
};
```

Step 4: Calling the Proxy from Your Frontend

Now, back in your browser-based calculator (Vanilla JS, React, or Vue), you simply call your deployed Worker URL.

Here is a minimal frontend fetch example:

``` js
async function getAIInsight(calculationResult, type) {
  try {
    const response = await fetch("https://your-worker-name.workers.dev/api/ai", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        context: `Principal: $1000, Rate: 5%, Years: 10. Future Value: $1628.89.`,
        promptType: type, // e.g., "finance"
      }),
    });

    if (!response.ok) throw new Error("Network error");
    const json = await response.json();
    return json.data; // The AI explanation
  } catch (error) {
    console.error("Failed to fetch AI insight:", error);
    return "Insight unavailable at this moment.";
  }
}
```

Crucially: Notice that the frontend never knows the API key. Even if an attacker inspects this network request, they only see a call to your Worker, not to Groq/OpenAI.

Trade-offs and Considerations

While this pattern is highly effective, it is not magic. Be aware of the following:

Factor Consideration

Latency Adding a proxy introduces an extra network hop. With Cloudflare's global network, this is usually < 50ms, but it's worth measuring.

Cost Cloudflare Workers have a generous free tier (100k requests/day). However, you are still paying for the LLM tokens. Implement strict max_tokens limits.

Rate Limiting Without accounts, how do you stop abuse? You can implement a simple IP-based rate limiter using Workers KV to prevent a single IP from draining your credits.

CORS If your frontend is on a specific domain, restrict Access-Control-Allow-Origin to that domain instead of using * in production.

A Real-World Implementation

This exact architecture is currently running in production on [AfriWidget.com](https://AfriWidget.com/). They use a Groq proxy to power AI explanations for their compound interest and GPA calculators.

The frontend sends only the numerical context—e.g., { principal: 5000, rate: 7, years: 20 }—to the proxy. The Worker appends the system prompt, calls Groq's Llama 3 model, and streams back a plain-English explanation of the financial projection.

Notably, they do not store user inputs or names anywhere in the proxy logs, ensuring that even if the Worker logs were compromised, no Personally Identifiable Information (PII) would leak.

Final Thoughts

Exposing API keys in client-side code is a shortcut that eventually becomes a financial liability. By spending 15 minutes setting up a Cloudflare Worker, you get:

The proxy pattern isn't just for AI APIs. Apply it to any third-party service that requires a secret. Your future self—and your bank account—will thank you.

What patterns do you use to secure external API calls? Let me know in the comments, or share your Worker implementation!
