Api Key Exposure on Firebase
How Api Key Exposure Manifests in Firebase
Firebase API key exposure in Firebase applications occurs through several Firebase-specific mechanisms that developers often misunderstand. Unlike traditional API keys that should remain secret, Firebase API keys are client-side credentials designed to be public. However, this public nature creates unique security challenges when combined with Firebase's authentication and database access patterns.
The most common Firebase API key exposure scenario involves misconfigured security rules. Developers often hardcode API keys in client applications and assume Firebase's security model will protect them. Consider this typical Firebase initialization pattern:
const firebaseConfig = {
apiKey: 'AIzaSyD4f8b9c0e1a2b3c4d5e6f7g8h9i0j1k2l3m',
authDomain: 'your-app.firebaseapp.com',
databaseURL: 'https://your-app.firebaseio.com',
projectId: 'your-app',
storageBucket: 'your-app.appspot.com',
messagingSenderId: '123456789',
appId: '1:123456789:web:abcdef1234567890'
};
firebase.initializeApp(firebaseConfig);This code appears in web applications, mobile apps, and even server-side code. The API key is embedded directly in the client bundle, making it trivially extractable through browser DevTools or APK decompilation.
The critical security issue isn't the API key exposure itself, but what happens when developers combine exposed keys with overly permissive security rules. A common pattern is:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
// OR using the deprecated Realtime Database rules
{
"rules": {
".read": true,
".write": true
}
}These rules allow anyone with a valid Firebase API key to read and write to your database. Since the API key is already exposed in client code, this effectively creates an open database accessible to anyone who inspects your application.
Another Firebase-specific exposure vector is the use of anonymous authentication with default security rules. Developers often implement:
firebase.auth().signInAnonymously()
.then((result) => {
// User is signed in anonymously
const user = result.user;
// Proceed with database operations
})
.catch((error) => {
// Handle errors
});When combined with permissive rules like allow read, write: if request.auth != null;, this allows any anonymous user to access data, creating a significant attack surface.
Firebase Storage also presents unique exposure risks. Developers often configure storage buckets with overly permissive rules:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read: if true;
allow write: if true;
}
}
}This allows anyone with the Firebase API key to upload, download, or delete files in your storage bucket, potentially leading to data exfiltration, malware distribution, or service abuse.
Firebase-Specific Detection
Detecting Firebase API key exposure requires understanding Firebase's unique architecture and security model. Traditional API key scanning tools often miss Firebase-specific issues because they don't understand Firebase's client-side authentication model.
middleBrick's Firebase-specific scanning identifies several key indicators of API key exposure and misconfiguration. The scanner examines your application for Firebase configuration objects and analyzes the security rules associated with your Firebase project.
Key detection patterns include:
Configuration Object Detection
The scanner identifies Firebase configuration objects in your application code, looking for patterns like:
const firebaseConfig = {
apiKey: '...', // Exposed API key
authDomain: '...', // Firebase domain
databaseURL: '...', // Database endpoint
projectId: '...', // Project identifier
storageBucket: '...', // Storage bucket
messagingSenderId: '...', // Messaging ID
appId: '...' // Application ID
};middleBrick extracts these configurations and cross-references them with your Firebase project settings to identify potential security misconfigurations.
Security Rules Analysis
The scanner analyzes your Firebase security rules for overly permissive configurations. It specifically looks for:
- Rules allowing
read, write: if trueor equivalent permissive conditions - Anonymous authentication rules that allow access without proper validation
- Storage rules with
allow read: if trueorallow write: if true - Missing authentication checks in Firestore rules
- Wildcard rules that match all documents or paths
Runtime API Key Validation
middleBrick performs active testing to validate API key functionality and identify what operations are permitted. This includes:
// Testing Firestore read access
const db = firebase.firestore();
db.collection('users').get()
.then(snapshot => {
// If this succeeds without authentication, rules are too permissive
});The scanner attempts various operations to determine the actual security posture of your Firebase deployment, mapping findings to OWASP API Top 10 categories like Broken Object Level Authorization (BOLA) and Excessive Data Exposure.
Anonymous Authentication Testing
middleBrick tests for anonymous authentication vulnerabilities by attempting to sign in anonymously and perform operations:
firebase.auth().signInAnonymously()
.then(() => {
// Test what operations are allowed for anonymous users
return firebase.firestore().collection('sensitive-data').get();
})
.catch(error => {
// Handle authentication errors
});This active testing reveals whether your Firebase project allows anonymous users to access sensitive data.
Firebase-Specific Remediation
Remediating Firebase API key exposure requires a multi-layered approach that leverages Firebase's native security features. The goal isn't to hide the API key (which is impossible in client applications) but to ensure it cannot be used maliciously.
Implement Proper Security Rules
The foundation of Firebase security is proper security rules. Replace permissive rules with authentication-based and data-specific rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Allow authenticated users to read public data
match /public/{document=**} {
allow read: if request.auth != null;
allow write: if false;
}
// Allow users to access their own data
match /users/{userId}/{document=**} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
// Admin-only collections
match /admin/{document=**} {
allow read, write: if request.auth.token.admin == true;
}
}
}
// Storage rules
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
// Allow authenticated users to read public files
match /public/{allPaths=**} {
allow read: if request.auth != null;
allow write: if false;
}
// User-specific storage
match /users/{userId}/{allPaths=**} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
}
}Validate API Key Usage
Firebase allows you to restrict API key usage to specific domains and applications. Configure these restrictions in the Firebase Console:
// In Firebase Console > Project Settings > Service Accounts
// Restrict API key to specific domains:
// - yourdomain.com
// - www.yourdomain.com
// - localhost for development
// For mobile apps, restrict to specific iOS bundle IDs and Android package namesImplement Server-Side Validation
For sensitive operations, use Cloud Functions to validate requests before they reach your database:
// Cloud Function to validate data before writing to Firestore
const functions = require('firebase-functions');
const admin = require('firebase-admin');
exports.validateUserData = functions.https.onCall((data, context) => {
// Check authentication
if (!context.auth) {
throw new functions.https.HttpsError(
'unauthenticated',
'User must be authenticated'
);
}
// Validate data structure
if (!data.email || !data.name) {
throw new functions.https.Https.HttpsError(
'invalid-argument',
'Email and name are required'
);
}
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(data.email)) {
throw new functions.https.Https.HttpsError(
'invalid-argument',
'Invalid email format'
);
}
// Return validated data for Firestore write
return {
success: true,
data: {
email: data.email,
name: data.name,
userId: context.auth.uid
}
};
});Use Custom Claims for Authorization
Implement role-based access control using custom claims:
// Set custom claims for admin users
admin.auth().setCustomUserClaims(uid, { admin: true })
.then(() => {
console.log('Admin claims set for user:', uid);
});
// Security rules using custom claims
match /admin/{document=**} {
allow read, write: if request.auth.token.admin == true;
}Monitor and Audit
Enable Firebase Audit Logging to monitor API key usage and detect suspicious patterns:
// Cloud Function to log suspicious activity
exports.logSuspiciousActivity = functions.firestore
.document('sensitive-data/{docId}')
.onWrite((change, context) => {
const before = change.before.data();
const after = change.after.data();
// Check for unusual data patterns
if (after && after.sensitiveField) {
console.log('Suspicious data written:', {
userId: context.auth.uid,
documentId: context.params.docId,
data: after
});
}
});Frequently Asked Questions
If Firebase API keys are meant to be public, what's the actual security risk?
allow read, write: if true), effectively creating an open database. The API key enables the connection, but the security rules determine what operations are allowed. middleBrick scans for both exposed keys and permissive rules to identify the complete attack surface.