← Back to DevBytes

Firebase Functions Security: IAM Policies and Network Security

Understanding Firebase Functions Security

Firebase Functions are built on top of Google Cloud Functions, which means they inherit the full security capabilities of Google Cloud Platform. When we talk about securing Firebase Functions, two critical layers demand your attention: IAM (Identity and Access Management) Policies and Network Security controls. IAM governs who can invoke, deploy, or manage your functions, while network security governs how and from where traffic reaches them.

Many developers deploy Firebase Functions with default settings, leaving them exposed to the public internet and accessible to anyone with the URL. In production environments, this is a serious vulnerability. A properly secured function stack combines granular IAM bindings with network-level ingress and egress restrictions to create defense in depth.

What Are IAM Policies for Firebase Functions?

IAM policies in Google Cloud are declarative statements that bind an identity (user, service account, or group) to a role on a specific resource. For Firebase Functions, the resource is the function itself, and the relevant roles determine what actions that identity can perform.

The four core Cloud Functions IAM roles you need to know:

Additionally, the Service Account attached to a function determines what that function itself can do inside Google Cloud—access Firestore, call other APIs, read from Storage buckets, and so on. This is distinct from who can invoke the function.

Why IAM Policies Matter

Without explicit IAM controls, you risk:

Locking down IAM ensures each identity has exactly the permissions it needs—no more, no less. This is the principle of least privilege.

How to Apply IAM Policies to Firebase Functions

You can grant the cloudfunctions.invoker role to specific users or service accounts, restricting who can trigger your function. Here is a complete walkthrough using the gcloud CLI.

Step 1: Deploy a Function with a Custom Service Account

First, create a dedicated service account for your function. This ensures the function itself operates with minimal permissions.

# Create a new service account
gcloud iam service-accounts create my-function-sa \
    --display-name="Service Account for My Function" \
    --project=your-project-id

# Grant only the permissions the function needs (e.g., Firestore read/write)
gcloud projects add-iam-policy-binding your-project-id \
    --member="serviceAccount:my-function-sa@your-project-id.iam.gserviceaccount.com" \
    --role="roles/datastore.user"

Now deploy a Firebase function using that service account:

// index.ts — a simple callable function
import * as functions from 'firebase-functions/v2';
import * as admin from 'firebase-admin';

admin.initializeApp();

export const getDocument = functions.https.onCall(async (request) => {
  const { docId } = request.data;
  const snapshot = await admin.firestore().collection('items').doc(docId).get();
  if (!snapshot.exists) {
    throw new functions.https.HttpsError('not-found', 'Document not found');
  }
  return snapshot.data();
});

Deploy with the custom service account:

# Deploy using Firebase CLI, specifying the service account
firebase deploy --only functions \
    --service-account="my-function-sa@your-project-id.iam.gserviceaccount.com"

Step 2: Restrict Who Can Invoke the Function

By default, HTTPS functions deployed with Firebase are publicly accessible. To lock it down, remove the allUsers grant and add specific invokers:

# First, find the fully qualified function resource name
FUNCTION_NAME="getDocument"
FUNCTION_RESOURCE="projects/your-project-id/locations/us-central1/functions/${FUNCTION_NAME}"

# Remove public access (allUsers)
gcloud functions remove-iam-policy-binding ${FUNCTION_NAME} \
    --region=us-central1 \
    --member="allUsers" \
    --role="roles/cloudfunctions.invoker"

# Grant invoker role to a specific user
gcloud functions add-iam-policy-binding ${FUNCTION_NAME} \
    --region=us-central1 \
    --member="user:developer@example.com" \
    --role="roles/cloudfunctions.invoker"

# Grant invoker role to a specific service account (for machine-to-machine calls)
gcloud functions add-iam-policy-binding ${FUNCTION_NAME} \
    --region=us-central1 \
    --member="serviceAccount:client-app-sa@your-project-id.iam.gserviceaccount.com" \
    --role="roles/cloudfunctions.invoker"

Step 3: Invoke the Function Authenticated from a Client

Once public access is removed, callers must present a valid credential. From a Firebase app, you can use the authenticated user token. From a backend service, you use a service account key or Application Default Credentials:

// Node.js client invoking the function with a service account
import { GoogleAuth } from 'google-auth-library';
import axios from 'axios';

async function callFunction() {
  const auth = new GoogleAuth({
    scopes: ['https://www.googleapis.com/auth/cloud-platform']
  });
  const client = await auth.getClient();
  const token = await client.getAccessToken();

  const response = await axios.post(
    'https://us-central1-your-project-id.cloudfunctions.net/getDocument',
    { docId: 'item-123' },
    {
      headers: {
        Authorization: `Bearer ${token.token}`,
        'Content-Type': 'application/json'
      }
    }
  );
  console.log(response.data);
}

callFunction().catch(console.error);

Network Security for Firebase Functions

IAM controls who can call your function. Network security controls where traffic originates and what your function can reach. This is especially important for functions that handle sensitive data or sit behind internal microservices.

Ingress Controls: Restricting Inbound Traffic

Cloud Functions V2 (which Firebase Functions v2 uses under the hood) supports two ingress settings:

Setting a function to internal-only is a powerful way to ensure it's only callable from other Cloud services you control, such as Cloud Run, App Engine, or another Cloud Function.

// Deploying a function with internal-only ingress
// firebase.json configuration snippet for v2 functions
{
  "functions": {
    "source": ".",
    "ingressSettings": {
      "getDocument": "ALLOW_INTERNAL_ONLY"
    }
  }
}

You can also set this at deploy time via the Firebase CLI or by using gcloud directly:

# Deploy with internal-only ingress using gcloud
gcloud functions deploy getDocument \
    --runtime=nodejs20 \
    --trigger-http \
    --ingress-settings=internal-only \
    --service-account="my-function-sa@your-project-id.iam.gserviceaccount.com" \
    --project=your-project-id

When ingress is internal-only, calling the function from your laptop or an external server will fail with a 403. Internal callers must still satisfy IAM requirements.

Egress Controls: VPC Connector and Outbound Traffic

By default, a Cloud Function's outbound traffic uses the public internet. If your function needs to reach private resources—like a Cloud SQL instance with a private IP, an internal API on Compute Engine, or a third-party service that requires a static IP—you need a VPC Connector.

A VPC Connector routes your function's egress traffic through your Virtual Private Cloud, where you can apply firewall rules, NAT configurations, and private DNS.

# Step 1: Create a VPC network and subnet (if you don't have one)
gcloud compute networks create my-private-network \
    --subnet-mode=custom \
    --project=your-project-id

gcloud compute networks subnets create my-subnet \
    --network=my-private-network \
    --region=us-central1 \
    --range=10.0.0.0/24 \
    --project=your-project-id

# Step 2: Create a Serverless VPC Connector
gcloud compute networks vpc-access connectors create my-connector \
    --network=my-private-network \
    --region=us-central1 \
    --range=10.8.0.0/28 \
    --project=your-project-id

# Step 3: Deploy the function with the connector, routing all egress through VPC
gcloud functions deploy getDocument \
    --runtime=nodejs20 \
    --trigger-http \
    --vpc-connector=projects/your-project-id/locations/us-central1/connectors/my-connector \
    --egress-settings=all-traffic \
    --ingress-settings=internal-only \
    --service-account="my-function-sa@your-project-id.iam.gserviceaccount.com"

The --egress-settings flag accepts two values:

Combining IAM and Network Security for Defense in Depth

The most secure posture layers both controls. Consider a function that processes payment data:

Here is how you would deploy such a function programmatically using the Cloud Functions API with full security parameters:

// Using @google-cloud/functions framework to deploy with full security
import { FunctionsClient } from '@google-cloud/functions';
import { protos } from '@google-cloud/functions';

const client = new FunctionsClient();
const projectPath = `projects/your-project-id/locations/us-central1`;

async function deploySecureFunction() {
  const functionConfig: protos.google.cloud.functions.v2.ICreateFunctionRequest = {
    parent: projectPath,
    functionId: 'processPayment',
    function: {
      name: `${projectPath}/functions/processPayment`,
      buildConfig: {
        runtime: 'nodejs20',
        entryPoint: 'processPayment',
        source: {
          storageSource: {
            bucket: 'your-deployment-bucket',
            object: 'function-source.zip'
          }
        },
        serviceAccount: 'projects/your-project-id/serviceAccounts/payment-fn-sa@your-project-id.iam.gserviceaccount.com'
      },
      serviceConfig: {
        ingressSettings: 'INTERNAL_ONLY',
        vpcConnector: 'projects/your-project-id/locations/us-central1/connectors/my-connector',
        egressSettings: 'ALL_TRAFFIC',
        // Only the backend service account can invoke
        securityLevel: 'SECURE_ALWAYS',
        // Environment variables for internal endpoints
        environmentVariables: {
          PAYMENT_API: 'http://10.0.0.50:8080',
          DB_HOST: 'cloud-sql-private-ip'
        }
      }
    }
  };

  const [operation] = await client.createFunction(functionConfig);
  console.log('Deployment started:', operation.name);
  
  // Wait for deployment to complete
  const [deployedFunction] = await operation.promise();
  console.log('Function deployed:', deployedFunction.name);
}

deploySecureFunction().catch(console.error);

Best Practices for Firebase Functions Security

Here is a distilled checklist of security practices you should adopt for every production Firebase Functions deployment:

Auditing and Monitoring Your Security Posture

Regularly check your function's IAM bindings. The following script lists all functions in a region and their current invoker grants:

#!/bin/bash
# audit-function-iam.sh — list invoker bindings for all functions in a region

PROJECT="your-project-id"
REGION="us-central1"

echo "Auditing IAM bindings for Cloud Functions in ${REGION}..."

for FUNC in $(gcloud functions list --project=${PROJECT} --format="value(name)" --filter="REGION:${REGION}"); do
  echo "---"
  echo "Function: ${FUNC}"
  gcloud functions get-iam-policy ${FUNC} --project=${PROJECT} --region=${REGION} \
    --format="table[box](bindings.members, bindings.role)"
done

For network security auditing, verify your function's ingress and egress settings with:

# Check ingress and egress settings for a specific function
gcloud functions describe getDocument \
    --project=your-project-id \
    --region=us-central1 \
    --format="json(serviceConfig.ingressSettings, serviceConfig.egressSettings, serviceConfig.vpcConnector)"

Conclusion

Securing Firebase Functions demands a deliberate combination of IAM policies and network controls. IAM defines the perimeter around who can interact with your functions, while network ingress and egress settings define how traffic flows. Together, they form a robust defense-in-depth architecture that protects your serverless workloads from unauthorized access, data exfiltration, and lateral movement within your cloud environment. Start by auditing your current deployments, remove public access where it isn't needed, assign minimal service account permissions, and route sensitive traffic through your VPC. These steps transform your Firebase Functions from publicly exposed endpoints into tightly controlled, production-grade cloud resources.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles