[{"data":1,"prerenderedAt":545},["ShallowReactive",2],{"blog-how-to/deploy-firebase-securely":3},{"id":4,"title":5,"body":6,"category":515,"date":516,"dateModified":516,"description":517,"draft":518,"extension":519,"faq":520,"featured":518,"headerVariant":530,"image":531,"keywords":532,"meta":533,"navigation":340,"ogDescription":534,"ogTitle":531,"path":535,"readTime":536,"schemaOrg":537,"schemaType":538,"seo":539,"sitemap":540,"stem":541,"tags":542,"twitterCard":543,"__hash__":544},"blog/blog/how-to/deploy-firebase-securely.md","How to Deploy Firebase Securely (2026)",{"type":7,"value":8,"toc":507},"minimark",[9,18,21,27,32,91,99,103,174,178,246,252,256,291,295,385,389,401,415,476,495,503],[10,11,12,13,17],"p",{},"The AI coding tool that built your Firebase app almost certainly deployed it with open Security Rules. In our scanner data, ",[14,15,16],"code",{},"allow read, write: if true"," shows up in roughly 40% of Firebase projects that were scaffolded by Cursor, Bolt, Lovable, or Replit. That rule lets anyone on the internet read and overwrite every document in your database with no login required.",[10,19,20],{},"That's the single biggest pre-launch risk. But it's not the only one. This guide covers all six things you need to check before your app goes live.",[22,23,24],"tldr",{},[10,25,26],{},"Open Firestore Security Rules are the #1 Firebase launch risk. Fix those first, then audit your env vars for secrets with VITE_/REACT_APP_ prefixes, move service account credentials server-side, and scan your deployed URL before sending traffic. The web apiKey is public by design and doesn't need rotation.",[28,29,31],"h2",{"id":30},"step-1-audit-your-security-rules-for-open-access-step-1","Step 1: Audit your Security Rules for open access {#step-1}",[33,34,36,44,54,61,88],"step",{"number":35},"1",[10,37,38,39,43],{},"Open the Firebase Console, go to ",[40,41,42],"strong",{},"Firestore Database > Rules"," and check for this pattern:",[45,46,51],"pre",{"className":47,"code":49,"language":50},[48],"language-text","rules_version = '2';\nservice cloud.firestore {\n  match /databases/{database}/documents {\n    match /{document=**} {\n      allow read, write: if true;\n    }\n  }\n}\n","text",[14,52,49],{"__ignoreMap":53},"",[10,55,56,57,60],{},"If you see ",[14,58,59],{},"if true",", your entire database is public. Deploy locked rules immediately:",[45,62,66],{"className":63,"code":64,"language":65,"meta":53,"style":53},"language-bash shiki shiki-themes github-light github-dark","firebase deploy --only firestore:rules\n","bash",[14,67,68],{"__ignoreMap":53},[69,70,73,77,81,85],"span",{"class":71,"line":72},"line",1,[69,74,76],{"class":75},"sScJk","firebase",[69,78,80],{"class":79},"sZZnC"," deploy",[69,82,84],{"class":83},"sj4cs"," --only",[69,86,87],{"class":79}," firestore:rules\n",[10,89,90],{},"Check Realtime Database and Cloud Storage rules too. AI tools often leave all three open.",[92,93,94],"danger-box",{},[10,95,96,98],{},[14,97,16],{}," means anyone who finds your Firebase project ID can read and delete all your user data. This gets found by automated scanners within hours of launch, not days.",[28,100,102],{"id":101},"step-2-fix-environment-variable-prefixes-step-2","Step 2: Fix environment variable prefixes {#step-2}",[33,104,106,121,124,149,152,157,163,168],{"number":105},"2",[10,107,108,109,112,113,116,117,120],{},"Prefixes like ",[14,110,111],{},"VITE_",", ",[14,114,115],{},"REACT_APP_",", and ",[14,118,119],{},"NEXT_PUBLIC_"," tell your bundler to compile that variable into the JavaScript file that ships to every browser. They're fine for public config like your Firebase project ID. They're dangerous for Admin SDK credentials.",[10,122,123],{},"Run this check in your project root:",[45,125,127],{"className":63,"code":126,"language":65,"meta":53,"style":53},"grep -r \"VITE_FIREBASE_ADMIN\\|REACT_APP_FIREBASE_ADMIN\\|NEXT_PUBLIC_FIREBASE_ADMIN\\|VITE_GOOGLE_APPLICATION\\|REACT_APP_GOOGLE_APPLICATION\" . --include=\"*.env*\"\n",[14,128,129],{"__ignoreMap":53},[69,130,131,134,137,140,143,146],{"class":71,"line":72},[69,132,133],{"class":75},"grep",[69,135,136],{"class":83}," -r",[69,138,139],{"class":79}," \"VITE_FIREBASE_ADMIN\\|REACT_APP_FIREBASE_ADMIN\\|NEXT_PUBLIC_FIREBASE_ADMIN\\|VITE_GOOGLE_APPLICATION\\|REACT_APP_GOOGLE_APPLICATION\"",[69,141,142],{"class":79}," .",[69,144,145],{"class":83}," --include=",[69,147,148],{"class":79},"\"*.env*\"\n",[10,150,151],{},"Any hits need to be renamed and moved to server-only variables. If the key has already been deployed with a public prefix, rotate it (see Step 3).",[10,153,154],{},[40,155,156],{},"Safe (public Firebase config, fine to prefix):",[45,158,161],{"className":159,"code":160,"language":50},[48],"VITE_FIREBASE_API_KEY=AIzaSy...          # public by design\nVITE_FIREBASE_PROJECT_ID=my-app-prod\n",[14,162,160],{"__ignoreMap":53},[10,164,165],{},[40,166,167],{},"Dangerous (must NOT have public prefix):",[45,169,172],{"className":170,"code":171,"language":50},[48],"FIREBASE_ADMIN_PRIVATE_KEY=\"-----BEGIN RSA PRIVATE KEY-----...\"\nFIREBASE_ADMIN_CLIENT_EMAIL=firebase-adminsdk@my-app.iam.gserviceaccount.com\nGOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json\n",[14,173,171],{"__ignoreMap":53},[28,175,177],{"id":176},"step-3-move-service-account-credentials-server-side-step-3","Step 3: Move service account credentials server-side {#step-3}",[33,179,181,192,203,206,237],{"number":180},"3",[10,182,183,184,187,188,191],{},"Your service account JSON file (the one you downloaded from Firebase Console with a ",[14,185,186],{},"private_key"," field) should never appear in client code. If your app runs ",[14,189,190],{},"firebase-admin"," anywhere that Vite or webpack might bundle it, move those calls to a server-only path.",[10,193,194,195,198,199,202],{},"For Next.js, put Admin SDK initialization in ",[14,196,197],{},"app/api/"," routes or ",[14,200,201],{},"lib/"," files that are imported only from API routes. For Vite apps, proxy calls through a backend (Express, Netlify Functions, Cloud Functions).",[10,204,205],{},"Check your service account isn't committed to git:",[45,207,209],{"className":63,"code":208,"language":65,"meta":53,"style":53},"git log --all --full-history -- \"*service-account*\" \"*serviceAccount*\" \"*.json\"\n",[14,210,211],{"__ignoreMap":53},[69,212,213,216,219,222,225,228,231,234],{"class":71,"line":72},[69,214,215],{"class":75},"git",[69,217,218],{"class":79}," log",[69,220,221],{"class":83}," --all",[69,223,224],{"class":83}," --full-history",[69,226,227],{"class":83}," --",[69,229,230],{"class":79}," \"*service-account*\"",[69,232,233],{"class":79}," \"*serviceAccount*\"",[69,235,236],{"class":79}," \"*.json\"\n",[10,238,239,240,245],{},"If you get output, the credentials are in your git history. Rotate the key in Firebase Console (IAM & Admin > Service Accounts), then ",[241,242,244],"a",{"href":243},"/blog/how-to/remove-secrets-git-history","remove the file from git history",".",[247,248,249],"warning-box",{},[10,250,251],{},"Rotating the service account key isn't enough if it's still in git history. Anyone who cloned or forked your repo still has the old key. Remove it from history AND revoke it in the Firebase Console.",[28,253,255],{"id":254},"step-4-write-scoped-security-rules-step-4","Step 4: Write scoped Security Rules {#step-4}",[33,257,259,262,268,271,288],{"number":258},"4",[10,260,261],{},"Replace open rules with rules that actually check who's asking. The minimal pattern for user-owned data:",[45,263,266],{"className":264,"code":265,"language":50},[48],"rules_version = '2';\nservice cloud.firestore {\n  match /databases/{database}/documents {\n    match /users/{userId} {\n      allow read, write: if request.auth != null\n                          && request.auth.uid == userId;\n    }\n\n    match /posts/{postId} {\n      allow read: if resource.data.published == true;\n      allow write: if request.auth != null\n                    && request.auth.uid == resource.data.authorId;\n    }\n  }\n}\n",[14,267,265],{"__ignoreMap":53},[10,269,270],{},"Test in the Firebase Emulator before deploying:",[45,272,274],{"className":63,"code":273,"language":65,"meta":53,"style":53},"firebase emulators:start --only firestore\n",[14,275,276],{"__ignoreMap":53},[69,277,278,280,283,285],{"class":71,"line":72},[69,279,76],{"class":75},[69,281,282],{"class":79}," emulators:start",[69,284,84],{"class":83},[69,286,287],{"class":79}," firestore\n",[10,289,290],{},"Then run your security rules test suite against it. Never push untested rules to production.",[28,292,294],{"id":293},"step-5-enable-firebase-app-check-step-5","Step 5: Enable Firebase App Check {#step-5}",[33,296,298,301,304,317,377,382],{"number":297},"5",[10,299,300],{},"App Check adds a layer between your app and Firebase backends: it verifies that requests come from your actual client, not a bot reusing your public Firebase config.",[10,302,303],{},"For web apps, register with reCAPTCHA v3:",[305,306,307,311,314],"ol",{},[308,309,310],"li",{},"Firebase Console > App Check > Get started",[308,312,313],{},"Register your web app with reCAPTCHA v3 (you'll need a reCAPTCHA site key from Google)",[308,315,316],{},"In your Firebase initialization:",[45,318,322],{"className":319,"code":320,"language":321,"meta":53,"style":53},"language-javascript shiki shiki-themes github-light github-dark","import { initializeApp } from 'firebase/app';\nimport { initializeAppCheck, ReCaptchaV3Provider } from 'firebase/app-check';\n\nconst app = initializeApp(firebaseConfig);\n\ninitializeAppCheck(app, {\n  provider: new ReCaptchaV3Provider('YOUR_RECAPTCHA_SITE_KEY'),\n  isTokenAutoRefreshEnabled: true,\n});\n","javascript",[14,323,324,329,335,342,348,353,359,365,371],{"__ignoreMap":53},[69,325,326],{"class":71,"line":72},[69,327,328],{},"import { initializeApp } from 'firebase/app';\n",[69,330,332],{"class":71,"line":331},2,[69,333,334],{},"import { initializeAppCheck, ReCaptchaV3Provider } from 'firebase/app-check';\n",[69,336,338],{"class":71,"line":337},3,[69,339,341],{"emptyLinePlaceholder":340},true,"\n",[69,343,345],{"class":71,"line":344},4,[69,346,347],{},"const app = initializeApp(firebaseConfig);\n",[69,349,351],{"class":71,"line":350},5,[69,352,341],{"emptyLinePlaceholder":340},[69,354,356],{"class":71,"line":355},6,[69,357,358],{},"initializeAppCheck(app, {\n",[69,360,362],{"class":71,"line":361},7,[69,363,364],{},"  provider: new ReCaptchaV3Provider('YOUR_RECAPTCHA_SITE_KEY'),\n",[69,366,368],{"class":71,"line":367},8,[69,369,370],{},"  isTokenAutoRefreshEnabled: true,\n",[69,372,374],{"class":71,"line":373},9,[69,375,376],{},"});\n",[305,378,379],{"start":344},[308,380,381],{},"In Firebase Console, enable App Check enforcement for Firestore and Storage after testing in debug mode",[10,383,384],{},"App Check doesn't replace Security Rules. Authenticated users who have a valid token can still abuse your rules if they're too permissive. It's an extra barrier against unauthenticated abuse.",[28,386,388],{"id":387},"step-6-scan-before-going-live-step-6","Step 6: Scan before going live {#step-6}",[33,390,392,395,398],{"number":391},"6",[10,393,394],{},"Before sending real traffic, run an external scan on your deployed URL. A scanner checks for misconfigured headers, exposed config, and open CORS policies that you might miss by looking at the source code alone.",[10,396,397],{},"CheckYourVibe scans your live URL and flags open Firebase rules, exposed API keys in JavaScript bundles, and missing security headers in one pass.",[10,399,400],{},"Run the scan, review the findings, and fix anything critical before announcing your launch.",[402,403,404],"tip-box",{},[10,405,406,407,410,411,414],{},"Run a fresh scan after every significant deploy, not just at initial launch. Security rules can be accidentally reverted during a ",[14,408,409],{},"firebase deploy"," if you're not tracking your ",[14,412,413],{},"firestore.rules"," file in version control.",[416,417,418,434,447,461,470],"faq-section",{},[419,420,422],"faq-item",{"question":421},"What are the most common Firebase security mistakes at launch?",[10,423,424,425,427,428,430,431,433],{},"Open Security Rules (",[14,426,16],{},"), service account JSON keys committed to git, and secrets prefixed with ",[14,429,111],{}," or ",[14,432,115],{}," that end up in the client bundle. CheckYourVibe finds at least one of these in roughly 40% of Firebase apps scanned.",[419,435,437],{"question":436},"Is the Firebase web apiKey a secret I need to protect?",[10,438,439,440,443,444,446],{},"No. The web ",[14,441,442],{},"apiKey"," is designed to be public. It just identifies your project. The real secrets are your service account JSON private key and any Admin SDK credentials. Rotate those if they leak; the web ",[14,445,442],{}," alone doesn't give anyone write access.",[419,448,450],{"question":449},"How do I fix 'allow read, write: if true' in Firestore?",[10,451,452,453,456,457,460],{},"Replace it with user-scoped rules: ",[14,454,455],{},"allow read, write: if request.auth != null && request.auth.uid == resource.data.userId",". Deploy with ",[14,458,459],{},"firebase deploy --only firestore:rules"," and test in the Firebase Emulator first.",[419,462,464],{"question":463},"Can I use firebase-admin in a Vite or Next.js client component?",[10,465,466,467,469],{},"No. ",[14,468,190],{}," is Node.js-only and must run in a server environment (Cloud Function, API route, or server action). Importing it in client code will either fail at build time or expose your service account credentials in the browser.",[419,471,473],{"question":472},"What does Firebase App Check actually do?",[10,474,475],{},"App Check verifies that requests to your Firebase backend come from your actual app (not a bot, a scraper, or someone using your config to hit your Firestore directly). It's not a substitute for Security Rules, but it reduces automated abuse.",[477,478,479,485,490],"related-articles",{},[480,481],"related-card",{"description":482,"href":483,"title":484},"Firebase's web apiKey is not secret. Your service account JSON key is. Identify what leaked, rotate the right credential, and lock your Security Rules.","/blog/how-to/fix-firebase-api-key-exposure","How to Fix Firebase API Key Exposure (2026)",[480,486],{"description":487,"href":488,"title":489},"20-item checklist covering Firestore rules, Authentication, Storage, and Cloud Functions security before you go live.","/blog/checklists/firebase-security-checklist","Firebase Security Checklist",[480,491],{"description":492,"href":493,"title":494},"Security analysis of Firebase covering Firestore rules, Authentication, and common misconfigurations in production apps.","/blog/is-safe/firebase","Is Firebase Safe?",[496,497,500],"cta-box",{"href":498,"label":499},"/","Start Free Scan",[10,501,502],{},"Run a free CheckYourVibe scan before your Firebase app goes live. We check for open Security Rules, exposed credentials, and missing headers in one pass.",[504,505,506],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":53,"searchDepth":331,"depth":331,"links":508},[509,510,511,512,513,514],{"id":30,"depth":331,"text":31},{"id":101,"depth":331,"text":102},{"id":176,"depth":331,"text":177},{"id":254,"depth":331,"text":255},{"id":293,"depth":331,"text":294},{"id":387,"depth":331,"text":388},"how-to","2026-06-15","Six steps to deploy your Firebase app without leaving open Security Rules, exposed service account keys, or secrets in your client bundle.",false,"md",[521,523,525,527,529],{"question":421,"answer":522},"Open Security Rules ('allow read, write: if true'), service account JSON keys committed to git, and secrets prefixed with VITE_ or REACT_APP_ that end up in the client bundle. CheckYourVibe finds at least one of these in roughly 40% of Firebase apps scanned.",{"question":436,"answer":524},"No. The web apiKey is designed to be public. It just identifies your project. The real secrets are your service account JSON private key and any Admin SDK credentials. Rotate those if they leak; the web apiKey alone doesn't give anyone write access.",{"question":449,"answer":526},"Replace it with user-scoped rules: 'allow read, write: if request.auth != null && request.auth.uid == resource.data.userId'. Deploy with 'firebase deploy --only firestore:rules' and test in the Firebase Emulator first.",{"question":463,"answer":528},"No. firebase-admin is Node.js-only and must run in a server environment (Cloud Function, API route, or server action). Importing it in client code will either fail at build time or expose your service account credentials in the browser.",{"question":472,"answer":475},"yellow",null,"deploy firebase securely, firebase security rules, firebase service account key, firebase production security, firebase app check, firebase environment variables",{},"Deploy Firebase securely: fix open Security Rules, move service account keys server-side, and scan before launch.","/blog/how-to/deploy-firebase-securely","10 min read","[object Object]","HowTo",{"title":5,"description":517},{"loc":535},"blog/how-to/deploy-firebase-securely",[],"summary_large_image","d4bSTxJ_M_FMz8vVkb5c11inqdqoEo4vsurli69joQk",1784736388748]