{"slug": "topic-selected-option-a-purely-technical-building-a-secure-ai-proxy-for-browser", "title": "Topic selected: Option A – Purely Technical: \"Building a Secure AI Proxy for Browser Tools", "summary": "A developer demonstrates how to build a secure AI proxy using Cloudflare Workers to protect API keys when calling Groq or OpenAI from browser-based tools. The guide covers scaffolding a worker, storing keys as environment variables, and implementing request validation and CORS handling.", "body_md": "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.\n\nHere is the article, rewritten to be strictly technical, objective, and genuinely useful for dev.to readers.\n\nStop Exposing Your AI API Keys: Build a Secure Proxy with Cloudflare Workers\n\nWe 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.\n\nIt 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.\n\nThe 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?\n\nIn 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.\n\nThe Architecture: How It Works\n\nInstead of your frontend talking directly to the AI provider, we introduce a stateless middleware layer:\n\n```\nBrowser App → Cloudflare Worker (Proxy) → Groq/OpenAI API\n     ↑                ↑\n  (No API Key)   (API Key stored securely in Worker env vars)\n```\n\nThe Worker's responsibilities:\n\nStep 1: Scaffolding the Cloudflare Worker\n\nWe will use the new create-cloudflare CLI. Make sure you have Node.js installed.\n\n```\nnpm create cloudflare@latest ai-proxy\n```\n\nChoose \"Hello World\" worker and TypeScript. Once inside the directory, install the Groq SDK:\n\n```\nnpm install groq-sdk\n```\n\nStep 2: Securing the API Key\n\nNever hardcode keys. Cloudflare Workers expose environment variables securely.\n\nUpdate your wrangler.toml:\n\n```\nname = \"ai-proxy\"\nmain = \"src/index.ts\"\ncompatibility_date = \"2024-12-18\"\n\n[vars]\nGROQ_API_KEY = \"your-secret-key-here\" # Replace, but prefer using wrangler secret for production\n```\n\nFor production, set it as an actual secret to hide it from the dashboard:\n\n```\nnpx wrangler secret put GROQ_API_KEY\n```\n\nStep 3: Writing the Worker Logic\n\nWe need an endpoint that accepts POST requests, validates the input, calls Groq, and returns the result.\n\nHere is the full src/index.ts implementation:\n\n``` js\nimport { Groq } from \"groq-sdk\";\n\nexport interface Env {\n  GROQ_API_KEY: string;\n}\n\nexport default {\n  async fetch(request: Request, env: Env): Promise<Response> {\n    // 1. CORS Preflight (handle OPTIONS)\n    if (request.method === \"OPTIONS\") {\n      return new Response(null, {\n        headers: {\n          \"Access-Control-Allow-Origin\": \"*\",\n          \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n          \"Access-Control-Allow-Headers\": \"Content-Type\",\n        },\n      });\n    }\n\n    // 2. Only allow POST requests\n    if (request.method !== \"POST\") {\n      return new Response(JSON.stringify({ error: \"Method not allowed\" }), {\n        status: 405,\n        headers: { \"Content-Type\": \"application/json\" },\n      });\n    }\n\n    try {\n      // 3. Parse and sanitize input\n      const body = await request.json();\n      const { context, promptType } = body;\n\n      if (!context || typeof context !== \"string\") {\n        return new Response(\n          JSON.stringify({ error: \"Missing 'context' field\" }),\n          { status: 400, headers: { \"Content-Type\": \"application/json\" } }\n        );\n      }\n\n      // 4. Initialize Groq with the secret key from environment\n      const groq = new Groq({\n        apiKey: env.GROQ_API_KEY,\n      });\n\n      // 5. Construct a system prompt based on the type (e.g., finance, health)\n      let systemPrompt = \"You are a helpful financial assistant.\";\n      if (promptType === \"health\") {\n        systemPrompt =\n          \"You are a medical disclaimer assistant. Provide general wellness info only, no diagnoses.\";\n      }\n\n      // 6. Call the LLM\n      const chatCompletion = await groq.chat.completions.create({\n        messages: [\n          { role: \"system\", content: systemPrompt },\n          {\n            role: \"user\",\n            content: `Explain what these calculations mean in plain English: ${context}`,\n          },\n        ],\n        model: \"llama3-8b-8192\", // Fast and cheap Groq model\n        temperature: 0.5,\n        max_tokens: 200,\n      });\n\n      const reply = chatCompletion.choices[0]?.message?.content || \"No response generated.\";\n\n      // 7. Return the response to the browser with CORS headers\n      return new Response(\n        JSON.stringify({ success: true, data: reply }),\n        {\n          headers: {\n            \"Content-Type\": \"application/json\",\n            \"Access-Control-Allow-Origin\": \"*\",\n          },\n        }\n      );\n    } catch (error) {\n      console.error(\"Proxy error:\", error);\n      return new Response(\n        JSON.stringify({ error: \"Internal server error\" }),\n        { status: 500, headers: { \"Content-Type\": \"application/json\" } }\n      );\n    }\n  },\n};\n```\n\nStep 4: Calling the Proxy from Your Frontend\n\nNow, back in your browser-based calculator (Vanilla JS, React, or Vue), you simply call your deployed Worker URL.\n\nHere is a minimal frontend fetch example:\n\n``` js\nasync function getAIInsight(calculationResult, type) {\n  try {\n    const response = await fetch(\"https://your-worker-name.workers.dev/api/ai\", {\n      method: \"POST\",\n      headers: { \"Content-Type\": \"application/json\" },\n      body: JSON.stringify({\n        context: `Principal: $1000, Rate: 5%, Years: 10. Future Value: $1628.89.`,\n        promptType: type, // e.g., \"finance\"\n      }),\n    });\n\n    if (!response.ok) throw new Error(\"Network error\");\n    const json = await response.json();\n    return json.data; // The AI explanation\n  } catch (error) {\n    console.error(\"Failed to fetch AI insight:\", error);\n    return \"Insight unavailable at this moment.\";\n  }\n}\n```\n\nCrucially: 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.\n\nTrade-offs and Considerations\n\nWhile this pattern is highly effective, it is not magic. Be aware of the following:\n\nFactor Consideration\n\nLatency Adding a proxy introduces an extra network hop. With Cloudflare's global network, this is usually < 50ms, but it's worth measuring.\n\nCost Cloudflare Workers have a generous free tier (100k requests/day). However, you are still paying for the LLM tokens. Implement strict max_tokens limits.\n\nRate 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.\n\nCORS If your frontend is on a specific domain, restrict Access-Control-Allow-Origin to that domain instead of using * in production.\n\nA Real-World Implementation\n\nThis 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.\n\nThe 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.\n\nNotably, 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.\n\nFinal Thoughts\n\nExposing 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:\n\nThe 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.\n\nWhat patterns do you use to secure external API calls? Let me know in the comments, or share your Worker implementation!", "url": "https://wpnews.pro/news/topic-selected-option-a-purely-technical-building-a-secure-ai-proxy-for-browser", "canonical_source": "https://dev.to/obed_avorlenu/topic-selected-option-a-purely-technical-building-a-secure-ai-proxy-for-browser-tools-5bc", "published_at": "2026-08-09 18:20:32+00:00", "updated_at": "2026-08-09 18:47:16.061729+00:00", "lang": "en", "topics": ["developer-tools", "ai-infrastructure", "ai-products"], "entities": ["Cloudflare Workers", "Groq", "OpenAI", "Node.js", "TypeScript"], "alternates": {"html": "https://wpnews.pro/news/topic-selected-option-a-purely-technical-building-a-secure-ai-proxy-for-browser", "markdown": "https://wpnews.pro/news/topic-selected-option-a-purely-technical-building-a-secure-ai-proxy-for-browser.md", "text": "https://wpnews.pro/news/topic-selected-option-a-purely-technical-building-a-secure-ai-proxy-for-browser.txt", "jsonld": "https://wpnews.pro/news/topic-selected-option-a-purely-technical-building-a-secure-ai-proxy-for-browser.jsonld"}}