How to Secure Gemini API Keys in Google Cloud & Firebase
Anyone who follows Google Cloud on LinkedIn has likely seen countless posts tagging them to discuss the fact that billing has surged on…
How to Secure Gemini API Keys in Google Cloud & Firebase

Anyone who follows Google Cloud on LinkedIn has likely seen countless posts tagging them to discuss the fact that billing has surged on accounts because of Gemini API keys being discovered and used maliciously. Whilst I do, of course, find sympathy, it does reiterate the importance of keeping APIs and infrastructure secure.
With that in mind, I decided to put a quick guide together on how to secure these when building projects using Firebase Hosting, Firebase Cloud Functions, and Google Cloud Secret Manager. The example code can also be found in Github here.
If your front-end JavaScript looks similar to this:
const ai = new GoogleGenAI({ apiKey: "AIzaSyYourSecretKey..." });
Anyone can open the browser’s DevTools, head to the Network tab, and steal your key instantly.
Instead, we use a backend proxy. Your website’s front-end, deployed securely via Firebase Hosting, talks directly to a Firebase Cloud Function. This cloud function safely retrieves the API key from Google Cloud Secret Manager at runtime, makes the request to Gemini server-side, and passes the parsed answer back to your front-end.
Step 1: Build a Standalone Vault Project & Restrict the Key
As well as never exposing your API on the client-side, you also don’t want to store your API secrets in the same Google Cloud project where your production Firebase website lives.
Instead, you should adopt the following model:
- Project A (The Vault): A locked-down, standalone Google Cloud project used only for Secret Manager and your Gemini API keys.
- Project B (Your Firebase App): Your operational project handling front-end web hosting and backend cloud functions.
By isolating the key into a standalone project, you ensure that even if your main web application deployment is somehow compromised, an attacker cannot tamper with, view, or delete your foundational API credentials. As always, you should ensure that you are using two-factor authentication on all of your accounts.
Setting up the Vault and Locking Down the API Scope:
- Create a brand new Google Cloud Project called something like api-vault (you can, obviously, be more inventive than me!).
- Navigate to Secret Manager inside this project and enable the API.
- Click Create Secret.
- Name your secret GEMINI_API_KEY (again, you can change this name if you wish).
- In the Secret value box, paste your live Gemini API key from Google AI Studio.
- Click Create.

Step 2: Initialise Firebase & Connect Cross-Project IAM
Next, let’s minimise risks by setting up your local project environment and connecting your Firebase app to your standalone vault project.
For your reference, I have previously created an article on how to deploy websites using Firebase.
Initialise Firebase locally
In an empty directory on your machine, initialise your Firebase project:
npm install -g firebase-tools
firebase login
firebase init
Select Hosting and Functions from the setup menu. Choose JavaScript or TypeScript for Functions.
Grant Cross-Project Access to Firebase
You don’t need to generate long-lived JSON service account keys to let your backend read from your vault. Instead, you grant IAM permissions directly to your Firebase application’s computing identity.
- Find your Firebase project’s default compute service account. It typically follows this format: YOUR-FIREBASE-PROJECT-ID@appspot.gserviceaccount.com
- Switch back to your Standalone Vault Project in the Google Cloud Console that you used to setup the API key.
- Go to Secret Manager and click on your GEMINI_API_KEY secret.
- In the right-hand Permissions panel, click Add Principal.
- Paste that Firebase service account email as the new principal.
- Assign the precise role: Secret Manager Secret Accessor (roles/secretmanager.secretAccessor).
- Click Save.
Step 3: Write Your Secure Firebase Cloud Function
Navigate into your generated functions directory and install the Gen AI SDK and Secret Manager package:
cd functions
npm install @google/genai
npm install @google-cloud/secret-manager
Open functions/index.js and export an HTTPS Callable Function. Callable functions are perfect here because they automatically handle body parsing, authorisation, and CORS restrictions out of the box. There is also a link to GitHub below for each file to make it easier to copy.
import { onCall, HttpsError } from "firebase-functions/v2/https";
import { SecretManagerServiceClient } from "@google-cloud/secret-manager";
import { GoogleGenAI } from "@google/genai";
const secretClient = new SecretManagerServiceClient();
export const askGemini = onCall({
// Enforce App Check to prevent unauthorised domain or bot usage
enforceAppCheck: true
}, async (request) => {
// The user prompt is automatically parsed and found in request.data
const { prompt } = request.data;
if (!prompt) {
throw new HttpsError("invalid-argument", "The function must be called with a 'prompt'.");
}
try {
// Pull the secret from your standalone Vault Project at runtime
const vaultProjectId = 'company-api-vault-12345'; // <-- Use your actual Vault Project ID, ideally as an environment variable
const name = `projects/${vaultProjectId}/secrets/GEMINI_API_KEY/versions/latest`;
const [version] = await secretClient.accessSecretVersion({ name });
const apiKey = version.payload.data.toString('utf8');
// Initialise the Gemini SDK with the fetched key
const ai = new GoogleGenAI({ apiKey: apiKey });
// Request content generation from the model
const response = await ai.models.generateContent({
model: "gemini-2.5-flash",
contents: prompt,
});
// Return the response object back to the front-end website
return { text: response.text };
} catch (error) {
console.error("Gemini Backend Error:", error);
throw new HttpsError("internal", "Failed to generate content from Gemini.");
}
});
Step 4: Build Your Firebase Hosting Front-End
With your backend Cloud Function acting as a secure gateway, your front-end code remains lightweight and entirely oblivious to the actual API credentials.
Inside your project’s public/ folder, save the following code as index.html. It provides a responsive chat interface and utilises the client-side Firebase SDK to call your backend function.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gemini AI Assistant</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background-color: #f4f6f9;
color: #333;
margin: 0;
padding: 40px 20px;
display: flex;
justify-content: center;
}
.container {
width: 100%;
max-width: 600px;
background: #fff;
padding: 30px;
border-radius: 12px;
box-shadow: 0 4px 15px rgba(0,0,0,0.05);
}
h1 {
font-size: 24px;
margin-bottom: 20px;
color: #1a73e8;
}
label {
font-weight: 600;
display: block;
margin-bottom: 8px;
}
textarea {
width: 100%;
height: 100px;
padding: 12px;
border: 1px solid #dadce0;
border-radius: 8px;
resize: vertical;
font-size: 16px;
box-sizing: border-box;
margin-bottom: 15px;
}
textarea:focus {
outline: none;
border-color: #1a73e8;
}
button {
background-color: #1a73e8;
color: white;
border: none;
padding: 12px 24px;
font-size: 16px;
font-weight: 500;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.2s;
}
button:hover {
background-color: #1557b0;
}
button:disabled {
background-color: #aaacb0;
cursor: not-allowed;
}
.output-box {
margin-top: 25px;
padding: 15px;
background-color: #f8f9fa;
border-left: 4px solid #1a73e8;
border-radius: 4px;
min-height: 50px;
white-space: pre-wrap;
}
.error {
border-left-color: #d93025;
color: #c5221f;
background-color: #fce8e6;
}
</style>
</head>
<body>
<div class="container">
<h1>Gemini AI Assistant</h1>
<label for="user-prompt">What would you like to ask?</label>
<textarea id="user-prompt" placeholder="Type your prompt here..."></textarea>
<button id="submit-btn" onclick="askGemini()">Send to Gemini</button>
<label style="margin-top: 25px;">Response:</label>
<div id="ai-response" class="output-box">The AI's response will appear here...</div>
</div>
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-app.js";
import { getFunctions, httpsCallable } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-functions.js";
// Your Firebase configuration object from the Firebase Console
const firebaseConfig = {
apiKey: "abcd1234-your-api-key",
authDomain: "your-app-id.firebaseapp.com",
projectId: "your-app-id",
storageBucket: "your-app-id.appspot.com",
messagingSenderId: "1234567890",
appId: "1:123456:web:abcdef"
};
// Initialise Firebase Services
const app = initializeApp(firebaseConfig);
const functions = getFunctions(app);
// Create a pointer directly to our secure Cloud Function
const askGeminiCloudFunction = httpsCallable(functions, 'askGemini');
window.askGemini = async function() {
const userInput = document.getElementById('user-prompt').value.trim();
const outputDiv = document.getElementById('ai-response');
const submitBtn = document.getElementById('submit-btn');
if (!userInput) {
alert('Please enter a prompt first.');
return;
}
outputDiv.classList.remove('error');
outputDiv.innerText = "Thinking...";
submitBtn.disabled = true;
try {
// Securely execute the Cloud Function.
// The browser never directly touches the Gemini API or its private key.
const result = await askGeminiCloudFunction({ prompt: userInput });
outputDiv.innerText = result.data.text;
} catch (error) {
console.error("Failed to reach Cloud Function:", error);
outputDiv.classList.add('error');
outputDiv.innerText = "An error occurred while connecting to the secure backend.";
} finally {
submitBtn.disabled = false;
}
}
</script>
</body>
</html>
To deploy your entire ecosystem (both your web assets and your backend function), run this command from your root directory:
firebase deploy
Step 5: Activate Firebase App Check
Hiding your key in a Cloud Function prevents people from stealing it, but it doesn’t stop malicious actors from finding your Cloud Function URL and spamming it directly with scripts. This would still run up your Gemini bill and cause unauthorised usage of your budget.
Because we added enforceAppCheck: true inside our Cloud Function code, Firebase will instantly reject any traffic that doesn’t originate from your actual web application.
- Go to the Firebase Console and select your project.
- In the left-hand menu under Security, click on App Check.
- Go to the Apps tab and click Register next to your Web App.
- Select an attestation provider: reCAPTCHA Enterprise is the typical choice for web apps. Information can be found here on how to create the keys needed.
- Follow the quick onscreen prompts to add the reCAPTCHA keys to your frontend script. Firebase provides a simple copy-paste snippet to initialise App Check right next to your existing firebaseConfig.
- Click Save and you’re ready to go.
Now, anyone attempting to trigger your Cloud Function using standalone Python scripts, Postman clones, or curl commands will be blocked at the door with a 401 Unauthorized error before your code even executes.
The image below is taken from the example project built with the above code. I can neither confirm nor deny whether I edited the response…!

Final Production Checklist
Before final completion, I would advise going through these final checks:
- Verify API Scope Restrictions: Double check that your Gemini key inside your Vault Project is explicitly restricted only to the Gemini API. Never leave a key set to “Unrestricted”.
- Enable Firebase App Check: Ensure App Check is registered in your console as outlined in Step 5 so rogue scripts are instantly blocked from spamming your functions.
- Clean Git History: Ensure no hardcoded strings starting with AIzaSy were accidentally committed to your repo history. If they were, delete and rotate the key immediately.
- Establish Billing Caps: Set up budget alerts and hard cost limits in your Google Cloud Billing console to automatically shut down services if an unexpected traffic spike occurs.
Hopefully you found the above useful — do not hesitate to reach out with any questions.
메타데이터
- post_id
- e99b6deb45e0
- slug
- how-to-secure-gemini-api-keys-in-google-cloud-firebase-e99b6deb45e0
- url
- https://medium.com/@timhuttonco/how-to-secure-gemini-api-keys-in-google-cloud-firebase-e99b6deb45e0
- canonical_url
- https://medium.com/@timhuttonco/how-to-secure-gemini-api-keys-in-google-cloud-firebase-e99b6deb45e0
- author_url
- https://medium.com/@timhuttonco
- status
- ok
- fetched_at
- 2026-06-13 00:08:42