फ्रंटएंड JavaScript में API Key को बिना लीक किए कैसे छिपाएँ
ब्राउज़र का कोड डिज़ाइन से ही सार्वजनिक होता है, इसलिए React, Vue या सादे JavaScript बंडल में डाली गई कोई भी API key सेकंडों में कॉपी हो सकती है। यह आर्टिकल दिखाता है कि एक छोटा सर्वर प्रॉक्सी, रेट लिमिट, सीमित अधिकार वाले टोकन और तेज़ रोटेशन आपके क्रेडेंशियल्स को पहुँच से बाहर कैसे रखते हैं।
पिछले महीने बनाई कोई भी साइट खोलें, F12 दबाएँ और Network टैब पर क्लिक करें। आपके JavaScript द्वारा भेजा गया हर हेडर साफ़ टेक्स्ट में वहाँ दिख रहा होगा, जिसमें वह Authorization वैल्यू भी शामिल है जिसके बारे में आपको यक़ीन था कि कोई उसे नहीं ढूँढेगा। फ्रंटएंड JavaScript में API key कैसे छिपाएँ के पीछे यही असहज सच्चाई है: उसे वहाँ छिपाया नहीं जा सकता। आप जो कर सकते हैं वह यह है कि सीक्रेट को ब्राउज़र तक भेजना ही बंद करें, और कॉल आपके नियंत्रण वाला सर्वर आपके यूज़र्स की ओर से करे।
यह आर्टिकल बताता है कि यह असल में कैसे काम करता है। आप देखेंगे कि बंडलर एनवायरनमेंट वेरिएबल्स को कैसे लीक करते हैं, बॉट्स डिप्लॉय के कुछ ही मिनटों में टोकन कैसे ढूँढ लेते हैं, और Express या Cloudflare Workers पर एक छोटा प्रॉक्सी कैसे बनाएँ जो आपका क्रेडेंशियल सर्वर पर रखे। इसके बाद हम रेट लिमिट, इनपुट वैलिडेशन, प्रतिबंधित टोकन और उस दिन के लिए साफ़ रिस्पॉन्स प्लान जोड़ते हैं, जब कुछ फिर भी लीक हो जाए।
💡 संक्षिप्त जवाब: अगर कोई सीक्रेट ऐसे कोड में जाता है जो ब्राउज़र में चलता है, तो वह सार्वजनिक है। उसे छिपाने के लिए रिक्वेस्ट को बैकएंड पर ले जाएँ, स्ट्रिंग को एन्कोड, स्प्लिट या स्क्रैम्बल करके नहीं।
फ्रंटएंड कोड सीक्रेट क्यों नहीं रख सकता
ब्राउज़र आपका कोड डाउनलोड करके विज़िटर की मशीन पर चलाकर काम करता है। जो कुछ वह डाउनलोड करता है, विज़िटर उसे पढ़ सकता है: HTML, CSS, JavaScript बंडल, सोर्स मैप और आपके कोड की हर रिक्वेस्ट। कोई सेटिंग, फ्लैग या बिल्ड स्टेप उस व्यक्ति के लिए किसी स्ट्रिंग को अदृश्य नहीं बनाता जिसके कंप्यूटर पर वह चल रही है।
ब्राउज़र में सब कुछ पढ़ा जा सकता है
तीन जगहें बिना किसी हैकिंग स्किल के टोकन उजागर कर देती हैं:
Network टैब। हर रिक्वेस्ट अपना URL, हेडर और पेलोड दिखाती है। हेडर में मौजूद Bearer टोकन एक क्लिक की दूरी पर है।
Sources टैब। आपका बंडल वहीं है, और सोर्स मैप चालू हों तो आपकी ओरिजिनल फ़ाइलें भी, कमेंट्स समेत।
View source और curl। कोई भी आपका बंडल डाउनलोड करके grep चला सकता है, sk_ या pia_sk_ जैसे टोकन प्रीफ़िक्स खोजने के लिए।
बंडलर आपके वेरिएबल्स को इनलाइन कर देते हैं
एक आम गलतफ़हमी यह है: "मैंने इसे .env फ़ाइल में डाला है, तो यह प्राइवेट है।" .env फ़ाइल सचमुच प्राइवेट है। लेकिन आपका बंडलर उसके साथ क्या करता है, वह अलग कहानी है। Vite, Next.js और Create React App बिल्ड टाइम पर खास प्रीफ़िक्स वाले वेरिएबल्स को उनकी असली वैल्यू से बदल देते हैं।
फ्रेमवर्क
सार्वजनिक होने वाला प्रीफ़िक्स
क्या होता है
Vite
VITE_
वैल्यू बंडल में इनलाइन हो जाती है
Next.js
NEXT_PUBLIC_
वैल्यू क्लाइंट कोड में इनलाइन हो जाती है
Create React App
REACT_APP_
बिल्ड टाइम पर वैल्यू इनलाइन होती है
Nuxt
NUXT_PUBLIC_
वैल्यू पब्लिक रनटाइम कॉन्फ़िग में चली जाती है
तो VITE_PROVIDER_TOKEN=abc123 वाली .env फ़ाइल में लिखा वेरिएबल आख़िर में assets/index-xxxx.js के अंदर सीधी स्ट्रिंग "abc123" बन जाता है। बिना पब्लिक प्रीफ़िक्स वाले वेरिएबल्स क्लाइंट बंडल से बाहर रहते हैं, और यही वजह है कि सीक्रेट सर्वर की तरफ़ होना चाहिए।
ऑब्फ़सकेशन सिर्फ़ रफ़्तार घटाता है
Base64, स्ट्रिंग स्प्लिटिंग, उल्टे अक्षर, XOR की चालें: इनमें से कोई काम नहीं करता, क्योंकि रिक्वेस्ट भेजने से पहले आपके कोड को असली वैल्यू फिर से बनानी पड़ती है। रिक्वेस्ट निकलते ही Network टैब तैयार नतीजा दिखा देता है। ऑब्फ़सकेशन हमलावर को दस मिनट की हल्की परेशानी देता है और आपको स्थायी मेंटेनेंस का सिरदर्द देता है।
लीक असल में कैसे होते हैं
बॉट्स रिपॉज़िटरी और बंडल स्कैन करते हैं
सबसे तेज़ तरीका सबसे उबाऊ भी है: पेज खोलें, फ़ीचर चलाएँ, हेडर पढ़ें। किसी स्क्रिप्ट की ज़रूरत नहीं। ऑटोमेटेड स्कैनर और आगे जाते हैं। वे ज्ञात टोकन फ़ॉर्मेट खोजने के लिए सार्वजनिक रिपॉज़िटरी, npm पैकेज और लाइव साइटें क्रॉल करते हैं, और कई प्रोवाइडर पहचाने जा सकने वाले प्रीफ़िक्स (sk_, ghp_, pia_sk_) इसीलिए इस्तेमाल करते हैं ताकि स्कैनर उन्हें आसानी से पहचान सकें।
सार्वजनिक GitHub रिपॉज़िटरी पर पुश किया गया टोकन मिनटों में उठाया जा सकता है। कुछ प्रोवाइडर अपने आप स्कैन करके उसे रद्द कर देते हैं, जो अच्छी बात है, लेकिन यह कोई प्लान नहीं है।
लीक की असली कीमत
पे-पर-यूज़ APIs पर दिखने वाला नुकसान बिल होता है। छिपा हुआ नुकसान उससे भी बुरा है: खत्म हुए कोटा से आपका अपना ऐप ऑफ़लाइन हो जाना, आपके अकाउंट पर दर्ज दुरुपयोग, और ढीली परमिशन के साथ असली डेटा तक पहुँच।
लीक हुआ क्रेडेंशियल
आम दुरुपयोग
आपको क्या कीमत चुकानी पड़ती है
LLM टोकन
मुफ़्त चैटबॉट, स्पैम जनरेशन
टोकन बिल, रेट-लिमिट लॉकआउट
इमेज या वीडियो जनरेशन टोकन
बल्क रेंडर, रीसेल
GPU उपयोग का बिल
मैप्स या सर्च टोकन
बड़े पैमाने पर स्क्रेपिंग
कोटा खत्म होना
डेटाबेस या स्टोरेज सीक्रेट
रिकॉर्ड पढ़ना या मिटाना
डेटा ब्रीच
AI ऐप्स सबसे पसंदीदा निशाना हैं। GPT 5.6 Luna या Claude Sonnet 5 जैसे मॉडल प्रति टोकन बिल करते हैं, इसलिए चुराया गया क्रेडेंशियल सीधे आपके खर्च पर किसी और के लिए मुफ़्त कंप्यूट में बदल जाता है।
ब्राउज़र और API के बीच प्रॉक्सी लगाएँ
समाधान आर्किटेक्चर में है। ब्राउज़र सीधे प्रोवाइडर को कॉल करने के बजाय आपके सर्वर को कॉल करता है, और आपका सर्वर प्रोवाइडर को।
Browser -> POST /api/generate -> Your server -> Provider API
(holds the secret)
इस डिज़ाइन को ईमानदार रखने वाले तीन नियम:
सीक्रेट सिर्फ़ सर्वर के एनवायरनमेंट वेरिएबल्स में रहता है। कभी रिपॉज़िटरी में नहीं, कभी NEXT_PUBLIC_ जैसे वेरिएबल में नहीं।
ब्राउज़र सिर्फ़ यूज़र इनपुट भेजता है। एक प्रॉम्प्ट, एक ID, सूची से एक चुनाव। कभी URL, हेडर या मॉडल का नाम नहीं जिसे वह मनमर्ज़ी से चुने।
सर्वर तय करता है कि क्या अनुमति है। वह इनपुट वैलिडेट करता है, क्रेडेंशियल जोड़ता है, कॉल आगे भेजता है और सिर्फ़ वही फ़ील्ड लौटाता है जो पेज को चाहिए।
काम करने वाला Express प्रॉक्सी
यह उदाहरण एक इमेज रिक्वेस्ट को PicassoIA API पर भेजता है, जो Authorization हेडर में Bearer टोकन से प्रमाणित होता है और predictions /v1/models/{owner}/{name}/predictions पर देता है। यही आकार किसी भी दूसरे प्रोवाइडर के लिए काम करता है।
// server.js
import express from "express";
import rateLimit from "express-rate-limit";
const app = express();
app.use(express.json({ limit: "20kb" }));
app.use("/api/", rateLimit({ windowMs: 60_000, limit: 10 }));
const UPSTREAM =
"https://api.picassoia.com/v1/models/picassoia/picassoia-image/predictions";
app.post("/api/generate", async (req, res) => {
const { prompt } = req.body ?? {};
if (typeof prompt !== "string" || prompt.length === 0 || prompt.length > 500) {
return res.status(400).json({ error: "Prompt must be 1 to 500 characters." });
}
try {
const upstream = await fetch(UPSTREAM, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PICASSOIA_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ input: { prompt } }),
});
const data = await upstream.json();
// Return only what the browser needs, never the raw upstream body.
res.status(upstream.status).json({ id: data.id, status: data.status });
} catch {
res.status(502).json({ error: "Upstream request failed." });
}
});
app.listen(3000);
इसे node --env-file=.env server.js (Node 20.6 या नए) से शुरू करें, ताकि टोकन किसी अनट्रैक्ड फ़ाइल से आए। PicassoIA API एसिंक्रोनस है: आप एक prediction बनाते हैं, फिर उसका स्टेटस पोल करते हैं। उसी तरीके से बना एक दूसरा GET /api/result/:id रूट जोड़ें, और सटीक रिस्पॉन्स फ़ील्ड्स PicassoIA API पेज पर जाँचें।
Cloudflare Workers पर सर्वरलेस वर्ज़न
कोई सर्वर मेंटेन नहीं करना चाहते? एक Worker कम लाइनों में वही काम करता है। सीक्रेट को npx wrangler secret put PICASSOIA_TOKEN से स्टोर करें, तो वह कभी आपकी रिपॉज़िटरी को नहीं छूता।
Vercel Functions, Netlify Functions और AWS Lambda भी यही पैटर्न अपनाते हैं: एक छोटा रूट, प्लेटफ़ॉर्म की सेटिंग्स में एक सीक्रेट, और क्लाइंट कोड में कोई क्रेडेंशियल नहीं।
फ़िक्स के बाद फ्रंटएंड कोड
अब ब्राउज़र सिर्फ़ आपके अपने रूट से बात करता है:
async function generate(prompt) {
const res = await fetch("/api/generate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ prompt }),
});
if (!res.ok) throw new Error(`Request failed: ${res.status}`);
return res.json();
}
ऐप बिल्ड करें, बंडल खोलें और उसमें खोजें। वहाँ ढूँढने लायक कुछ नहीं बचना चाहिए।
प्रॉक्सी को भी लॉक करें
बिना किसी लिमिट वाला प्रॉक्सी अजनबियों के लिए आपका पैसा खर्च करने का बस एक आसान तरीका है। उसे सार्वजनिक एंडपॉइंट मानें, क्योंकि वह है।
💡 CORS के बारे में:Access-Control-Allow-Origin को अपने डोमेन पर सेट करने से दूसरी वेबसाइटें विज़िटर के ब्राउज़र से आपका प्रॉक्सी कॉल नहीं कर पातीं। यह curl या किसी स्क्रिप्ट के खिलाफ़ कुछ नहीं करता। असली सुरक्षा ऑथेंटिकेशन, रेट लिमिट और वैलिडेशन से आती है।
यूज़र या IP के हिसाब से रेट लिमिट
जब आपके पास लॉगिन हों तो ऑथेंटिकेटेड यूज़र के हिसाब से, और जब न हों तो IP एड्रेस के हिसाब से लिमिट लगाएँ। CDN या लोड बैलेंसर के पीछे, सुनिश्चित करें कि आपका फ्रेमवर्क असली क्लाइंट IP पढ़ता है (Express में इसके लिए trust proxy सही तरीके से सेट करना होता है), वरना हर विज़िटर एक ही बकेट साझा करेगा।
एंडपॉइंट का प्रकार
शुरुआती लिमिट
क्यों
टेक्स्ट जनरेशन
प्रति यूज़र प्रति मिनट 20 रिक्वेस्ट
हर कॉल सस्ती, स्पैम करना आसान
इमेज जनरेशन
प्रति यूज़र प्रति मिनट 5 रिक्वेस्ट
हर कॉल पर असली GPU खर्च
वीडियो जनरेशन
प्रति यूज़र प्रति मिनट 2 रिक्वेस्ट
धीमा और महँगा
सिर्फ़ पढ़ने वाली स्टेटस जाँच
प्रति यूज़र प्रति मिनट 60 रिक्वेस्ट
पोलिंग सामान्य है
इन संख्याओं को शुरुआती बिंदु मानें और असली ट्रैफ़िक के आधार पर उन्हें ट्यून करें।
हर इनपुट को वैलिडेट करें
ब्राउज़र की रिक्वेस्ट बॉडी को जैसी आती है वैसे आगे न भेजें। हर फ़ील्ड अलग से जाँचें:
प्रॉम्प्ट की लंबाई सीमित करें। ऊपर का उदाहरण 500 अक्षरों से ज़्यादा वाले इनपुट को ठुकरा देता है।
मॉडल्स की अनुमति-सूची बनाएँ। पेज "fast" या "quality" भेज सकता है, फिर सर्वर उन लेबलों को असली मॉडल नामों से मैप करे।
आउटपुट का आकार सीमित करें। हर कॉल के लिए आउटपुट टोकन या इमेज की अधिकतम संख्या तय करें।
अज्ञात फ़ील्ड्स ठुकराएँ। अगर स्कीमा में prompt लिखा है, तो उसके अलावा कुछ भी आगे नहीं जाएगा।
प्रोवाइडर पर बजट कैप सेट करें
ज़्यादातर प्रोवाइडर मासिक खर्च की सीमा और अलर्ट सेट करने देते हैं। उन्हें चालू करें। यह उस दिन का सेफ़्टी नेट है जब बाकी सारी परतें फेल हो जाएँ। साथ ही हर प्रोजेक्ट और हर एनवायरनमेंट के लिए अलग टोकन इस्तेमाल करें, ताकि एक टोकन रद्द करने से बाकी न रुकें।
सार्वजनिक टोकन कब ठीक है
हर क्रेडेंशियल सीक्रेट नहीं होता। कुछ ब्राउज़र के लिए ही बने हैं: Firebase वेब कॉन्फ़िगरेशन, Stripe के पब्लिशेबल टोकन (pk_ वाले) और Google Maps JavaScript टोकन। ये तभी सुरक्षित हैं जब आप इन्हें प्रतिबंधित करें।
डोमेन और स्कोप से प्रतिबंध लगाएँ
प्रोवाइडर का डैशबोर्ड खोलें और हर उपलब्ध प्रतिबंध लागू करें:
HTTP रेफ़रर लिमिट, ताकि टोकन सिर्फ़ yourdomain.com से काम करे।
API स्कोप लिमिट, ताकि Maps टोकन सिर्फ़ Maps को कॉल कर सके और कुछ नहीं।
रोज़ाना कोटा, ताकि दुरुपयोग की बाढ़ एक सीमा पर रुक जाए।
एक चेतावनी: रेफ़रर चेक Referer हेडर पर निर्भर करते हैं, और नॉन-ब्राउज़र क्लाइंट उसे नकली बना सकते हैं। प्रतिबंध आम दुरुपयोग को घटाते हैं, वे सार्वजनिक टोकन को सीक्रेट नहीं बना देते। मान छोटा, सीमित स्कोप वाला और बजट-कैप्ड रखें।
ब्राउज़र के लिए शॉर्ट-लिव्ड टोकन
कुछ काम आपके सर्वर से रिले करने के लिए बहुत भारी होते हैं, जैसे बड़े अपलोड या रियलटाइम स्ट्रीमिंग। इनके लिए टोकन एक्सचेंज इस्तेमाल करें:
यूज़र आपके बैकएंड में साइन इन करता है।
आपका बैकएंड प्रोवाइडर से एक शॉर्ट-लिव्ड, संकीर्ण स्कोप वाला टोकन माँगता है, या ऐसा JWT साइन करता है जो 5 से 15 मिनट में खत्म हो जाए।
ब्राउज़र उस अस्थायी टोकन को सीधे भारी रिक्वेस्ट के लिए इस्तेमाल करता है।
टोकन अपने आप खत्म हो जाता है, इसलिए कॉपी किया गया मान जल्द ही बेकार हो जाता है।
कई रियलटाइम और स्टोरेज प्रोवाइडर ठीक इसी पैटर्न के लिए एफ़िमरल टोकन देते हैं। आपका लंबे समय वाला सीक्रेट फिर भी कभी सर्वर से बाहर नहीं जाता।
लीक के बाद क्या करें
पहले रद्द करें, फिर जाँच करें
अगर कोई टोकन एक घंटे के लिए भी सार्वजनिक रहा हो, तो मान लें कि किसी ने उसे कॉपी कर लिया है। यह सूची क्रम से पूरी करें:
प्रोवाइडर डैशबोर्ड में टोकन को अभी रद्द करें या रोटेट करें।
नया टोकन सिर्फ़ अपने सर्वर एनवायरनमेंट में डिप्लॉय करें।
एक्सपोज़र अवधि के यूसेज लॉग पढ़ें और अनजान IP, मॉडल या उछाल ढूँढें।
कुछ और करने से पहले खर्च की सीमा कसें।
अपनी टीम को बताएँ, और जाँचें कि वही वैल्यू कहीं और दोबारा तो इस्तेमाल नहीं हुई।
Git हिस्ट्री और बंडल साफ़ करें
आपके सबसे नए कमिट से सीक्रेट हटाने से कुछ ठीक नहीं होता, क्योंकि हिस्ट्री में वह अब भी है। पुराने डिप्लॉयमेंट, CDN कैश और सार्वजनिक सोर्स मैप में भी वह हो सकता है। असली समाधान रोटेशन है। git filter-repo से हिस्ट्री फिर से लिखना उसके बाद की सफ़ाई है।
इसके बाद दोबारा होने की संभावना कम करें:
gitleaks जैसा प्री-कमिट स्कैनर जोड़ें।
अपने Git होस्ट में पुश प्रोटेक्शन और सीक्रेट स्कैनिंग चालू करें।
प्रोडक्शन में सोर्स मैप प्रकाशित न करें, या उन्हें सिर्फ़ अपने एरर ट्रैकर को दें।
एक CI स्टेप जोड़ें जो बिल्ट बंडल में ज्ञात टोकन प्रीफ़िक्स खोजे और मैच मिलने पर बिल्ड फ़ेल कर दे।
LLM से अपने बंडल का ऑडिट करें
इस काम के लिए LLM एक तेज़ दूसरी जोड़ी आँखों जैसा है। PicassoIA पर लीक खोजने और अपना प्रॉक्सी ड्राफ़्ट करने के लिए Claude Sonnet 5 का इस्तेमाल कैसे करें, यह यहाँ है:
पहले बिल्ड और स्कैन करें।npm run build चलाएँ, फिर grep -rE "sk_|pk_|pia_sk_|Bearer " dist/ चलाएँ ताकि स्पष्ट मामले आप खुद पकड़ सकें।
सिर्फ़ रिडैक्टेड कोड पेस्ट करें। वे फ़ाइलें शामिल करें जो नेटवर्क कॉल करती हैं, और हर असली वैल्यू की जगह REDACTED लिखें। किसी भी चैट टूल में लाइव सीक्रेट कभी पेस्ट न करें।
एक साफ़ सवाल पूछें। उदाहरण के लिए: "इस कोड में हर उस जगह की सूची बनाएँ जहाँ ब्राउज़र से क्रेडेंशियल भेजा जाता है, और हर एक को सर्वर रूट कॉल करने के लिए दोबारा लिखें।"
जवाब को ऊपर के नियमों से जाँचें। वैलिडेशन, रेट लिमिट और एरर हैंडलिंग देखें, फिर DevTools में टेस्ट करें।
💡 टिप: दूसरी राय के लिए वही प्रॉम्प्ट GPT 5.6 Sol पर चलाएँ, या Gemini 3.5 Flash से तेज़ पहला पास लें। अलग-अलग मॉडल अलग-अलग गलतियाँ पकड़ते हैं।
टोकन लीक किए बिना इमेज ऐप्स बनाएँ
ऊपर की हर बात तब और ज़्यादा ज़रूरी हो जाती है जब आपका ऐप इमेज या वीडियो बनाता है, क्योंकि हर कॉल GPU समय खर्च करती है। पैटर्न वही रहता है: पेज प्रॉम्प्ट इकट्ठा करता है, आपका प्रॉक्सी क्रेडेंशियल रखता है, और PicassoIA रेंडरिंग करता है।
अपने प्रोडक्ट के हिसाब से मॉडल चुनें। Flux 2 Pro विस्तृत फ़ोटोरियलिज़्म के लिए ठीक है, Seedream 4.5 कई तत्वों वाले प्रॉम्प्ट संभालता है, P-Image प्रीव्यू के लिए तेज़ विकल्प है, और GPT Image 2 तस्वीरों के अंदर टेक्स्ट के लिए मज़बूत है। मोशन के लिए, PicassoIA के पूरे मॉडल कैटलॉग में टेक्स्ट-टू-वीडियो मॉडल देखें।
अगले डिप्लॉय से पहले यह त्वरित चेकलिस्ट देखें:
बिल्ट बंडल या सोर्स मैप में कोई क्रेडेंशियल दिखाई न दे।
ब्राउज़र आपके प्रॉक्सी को कॉल करे, कभी प्रोवाइडर को नहीं।
प्रॉम्प्ट की लंबाई, मॉडल चुनाव और आउटपुट का आकार सर्वर पर वैलिडेट हो।
रेट लिमिट और प्रोवाइडर पर खर्च की सीमा सक्रिय हो।
कोई भी सार्वजनिक टोकन डोमेन, स्कोप और कोटा से प्रतिबंधित हो।
हर टोकन को रद्द करने के स्टेप्स उसकी ज़रूरत पड़ने से पहले पता हों।
देखने के लिए तैयार हैं कि यह कैसे काम करता है? PicassoIA खोलें, ऊपर के मॉडल्स से कुछ इमेज बनाएँ, फिर वही प्रॉम्प्ट अपने प्रॉक्सी में जोड़ें। अलग-अलग स्टाइल और प्रॉम्प्ट आज़माएँ, और ऐसा ऐप बनाएँ जिसमें विज़िटर ब्राउज़र से सिर्फ़ तैयार तस्वीर कॉपी कर सकें।