From 83811f2b34933389ea46ad631412d86cc801d71c Mon Sep 17 00:00:00 2001 From: Muhamad Sazwan Bin Ismail Date: Fri, 17 Oct 2025 20:49:49 +0800 Subject: [PATCH] Secure Fairbase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I'll provide you with comprehensive Firebase security implementation guidance. Here's a detailed security setup: ## 1. Firebase Security Rules & Configuration ### Firebase Security Rules (`firebase.json`) ```json { "firestore": { "rules": "firestore.rules", "indexes": "firestore.indexes.json" }, "storage": { "rules": "storage.rules" }, "hosting": { "public": "public", "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], "headers": [ { "source": "**", "headers": [ { "key": "X-Frame-Options", "value": "DENY" }, { "key": "X-Content-Type-Options", "value": "nosniff" }, { "key": "X-XSS-Protection", "value": "1; mode=block" }, { "key": "Strict-Transport-Security", "value": "max-age=31536000; includeSubDomains" } ] } ], "rewrites": [ { "source": "**", "destination": "/index.html" } ] }, "emulators": { "auth": { "port": 9099 }, "firestore": { "port": 8080 }, "storage": { "port": 9199 }, "ui": { "enabled": true } } } ``` ## 2. Firestore Security Rules ### `firestore.rules` ```javascript rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // Global read/write control - adjust as needed match /{document=**} { allow read, write: if request.auth != null; } // Users can only access their own user data match /users/{userId} { allow read, write: if request.auth != null && request.auth.uid == userId; allow create: if request.auth != null && request.auth.uid == userId && request.resource.data.keys().hasAll(['email', 'createdAt']) && request.resource.data.email is string && request.resource.data.createdAt is timestamp; } // Posts collection - users can read all, but only write their own match /posts/{postId} { allow read: if request.auth != null; allow create: if request.auth != null && request.auth.uid == request.resource.data.authorId && request.resource.data.keys().hasAll(['title', 'content', 'createdAt']) && request.resource.data.title is string && request.resource.data.content is string && request.resource.data.createdAt is timestamp; allow update, delete: if request.auth != null && request.auth.uid == resource.data.authorId; } // Comments - users can read all, but only write their own match /posts/{postId}/comments/{commentId} { allow read: if request.auth != null; allow create: if request.auth != null && request.auth.uid == request.resource.data.authorId && request.resource.data.keys().hasAll(['content', 'createdAt']) && request.resource.data.content is string && request.resource.data.createdAt is timestamp; allow update, delete: if request.auth != null && request.auth.uid == resource.data.authorId; } // Admin role check function function isAdmin() { return request.auth.token.admin == true; } // Admin-only collections match /admin/{document=**} { allow read, write: if isAdmin(); } // Audit trail - read only for admins match /audit/{document=**} { allow read: if isAdmin(); allow write: if request.auth != null; } } } ``` ## 3. Firebase Storage Security Rules ### `storage.rules` ```javascript rules_version = '2'; service firebase.storage { match /b/{bucket}/o { // Users can only upload to their own folder match /users/{userId}/{allPaths=**} { allow read: if request.auth != null; allow write: if request.auth != null && request.auth.uid == userId; } // Profile pictures - users can read all, but only write their own match /profile_pictures/{userId} { allow read: if request.auth != null; allow write: if request.auth != null && request.auth.uid == userId; } // Post images - readable by all authenticated users match /post_images/{postId}/{imageId} { allow read: if request.auth != null; allow write: if request.auth != null && exists(/databases/$(database)/documents/posts/$(postId)) && get(/databases/$(database)/documents/posts/$(postId)).data.authorId == request.auth.uid; } // Public read-only files match /public/{allPaths=**} { allow read: if true; allow write: if request.auth != null && isAdmin(); } function isAdmin() { return request.auth.token.admin == true; } } } ``` ## 4. Firebase Authentication Security ### Security Configuration ```javascript // firebase-auth-security.js import { initializeApp } from 'firebase/app'; import { getAuth, createUserWithEmailAndPassword, signInWithEmailAndPassword, signOut, updateProfile, sendEmailVerification, sendPasswordResetEmail, setPersistence, browserSessionPersistence, browserLocalPersistence, onAuthStateChanged } from 'firebase/auth'; const firebaseConfig = { apiKey: process.env.REACT_APP_FIREBASE_API_KEY, authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN, projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID, storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET, messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID, appId: process.env.REACT_APP_FIREBASE_APP_ID }; const app = initializeApp(firebaseConfig); const auth = getAuth(app); class FirebaseAuthSecurity { constructor() { this.auth = auth; this.setupSecurityPolicies(); } setupSecurityPolicies() { // Set session persistence based on user preference setPersistence(this.auth, browserLocalPersistence); // Monitor auth state for security onAuthStateChanged(this.auth, (user) => { if (user) { this.logSecurityEvent('user_signed_in', user.uid); this.validateUserSession(user); } else { this.logSecurityEvent('user_signed_out'); } }); } async validateUserSession(user) { // Check if email is verified if (!user.emailVerified) { await sendEmailVerification(user); throw new Error('Please verify your email before proceeding'); } // Check if token is recent (less than 1 hour old) const tokenTime = user.metadata.lastSignInTime; const currentTime = new Date(); const timeDiff = (currentTime - new Date(tokenTime)) / (1000 * 60 * 60); if (timeDiff > 24) { await this.auth.signOut(); throw new Error('Session expired. Please sign in again.'); } } async secureSignUp(email, password, displayName) { try { // Validate password strength this.validatePassword(password); const userCredential = await createUserWithEmailAndPassword( this.auth, email, password ); // Update profile await updateProfile(userCredential.user, { displayName: displayName }); // Send email verification await sendEmailVerification(userCredential.user); // Log security event this.logSecurityEvent('user_registered', userCredential.user.uid); return userCredential; } catch (error) { this.logSecurityEvent('registration_failed', null, error.message); throw error; } } async secureSignIn(email, password) { try { const userCredential = await signInWithEmailAndPassword( this.auth, email, password ); // Validate session await this.validateUserSession(userCredential.user); this.logSecurityEvent('login_success', userCredential.user.uid); return userCredential; } catch (error) { this.logSecurityEvent('login_failed', null, error.message); throw error; } } validatePassword(password) { const minLength = 8; const hasUpperCase = /[A-Z]/.test(password); const hasLowerCase = /[a-z]/.test(password); const hasNumbers = /\d/.test(password); const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password); if (password.length < minLength) { throw new Error('Password must be at least 8 characters long'); } if (!hasUpperCase) { throw new Error('Password must contain at least one uppercase letter'); } if (!hasLowerCase) { throw new Error('Password must contain at least one lowercase letter'); } if (!hasNumbers) { throw new Error('Password must contain at least one number'); } if (!hasSpecialChar) { throw new Error('Password must contain at least one special character'); } } async logSecurityEvent(eventType, userId = null, details = null) { // Log to Firestore for audit trail const securityLog = { eventType, userId, timestamp: new Date(), userAgent: navigator.userAgent, ipAddress: await this.getClientIP(), details }; // In a real app, you'd write this to Firestore console.log('Security Event:', securityLog); } async getClientIP() { // This would typically be handled by a cloud function // that can see the real IP address return 'client-ip-not-available-in-client'; } } export default FirebaseAuthSecurity; ``` ## 5. Firebase App Check Implementation ```javascript // firebase-app-check.js import { initializeAppCheck, ReCaptchaV3Provider } from 'firebase/app-check'; const appCheck = initializeAppCheck(app, { provider: new ReCaptchaV3Provider('your-recaptcha-v3-site-key'), isTokenAutoRefreshEnabled: true }); ``` ## 6. Firebase Security Functions ### Cloud Functions for Security ```javascript // functions/security.js const functions = require('firebase-functions'); const admin = require('firebase-admin'); admin.initializeApp(); // Security: Monitor for suspicious activities exports.monitorSuspiciousActivities = functions.firestore .document('audit/{auditId}') .onCreate(async (snapshot, context) => { const auditData = snapshot.data(); // Check for multiple failed logins if (auditData.eventType === 'login_failed') { const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000); const failedLogins = await admin.firestore() .collection('audit') .where('eventType', '==', 'login_failed') .where('timestamp', '>', oneHourAgo) .where('userId', '==', auditData.userId) .get(); if (failedLogins.size > 5) { // Suspend account or send alert await admin.firestore() .collection('security_alerts') .add({ type: 'suspicious_login_attempts', userId: auditData.userId, count: failedLogins.size, timestamp: new Date() }); } } }); // Security: Clean up old audit logs exports.cleanupOldLogs = functions.pubsub .schedule('every 24 hours') .onRun(async (context) => { const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); const oldLogs = await admin.firestore() .collection('audit') .where('timestamp', '<', thirtyDaysAgo) .get(); const batch = admin.firestore().batch(); oldLogs.docs.forEach(doc => { batch.delete(doc.ref); }); await batch.commit(); console.log(`Cleaned up ${oldLogs.size} old audit logs`); }); ``` ## 7. Environment Security Configuration ### `.env` file ```env # Firebase Configuration REACT_APP_FIREBASE_API_KEY=your_api_key_here REACT_APP_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com REACT_APP_FIREBASE_PROJECT_ID=your-project-id REACT_APP_FIREBASE_STORAGE_BUCKET=your-project.appspot.com REACT_APP_FIREBASE_MESSAGING_SENDER_ID=your_sender_id REACT_APP_FIREBASE_APP_ID=your_app_id # Security REACT_APP_RECAPTCHA_SITE_KEY=your_recaptcha_site_key REACT_APP_SECURITY_LOG_RETENTION_DAYS=30 ``` ## 8. Security Best Practices Implementation ```javascript // security-best-practices.js export class SecurityBestPractices { static async validateFileUpload(file) { // Check file size (max 5MB) const maxSize = 5 * 1024 * 1024; if (file.size > maxSize) { throw new Error('File size too large. Maximum 5MB allowed.'); } // Check file type const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf']; if (!allowedTypes.includes(file.type)) { throw new Error('File type not allowed.'); } // Check for potential malicious files if (file.name.includes('..') || file.name.includes('/')) { throw new Error('Invalid file name.'); } } static sanitizeUserInput(input) { // Basic XSS prevention return input .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, ''') .replace(/\//g, '/'); } static validateEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } static generateSecureToken(length = 32) { const array = new Uint8Array(length); crypto.getRandomValues(array); return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join(''); } } ``` ## 9. Usage Example ```javascript // app.js import FirebaseAuthSecurity from './firebase-auth-security'; import { SecurityBestPractices } from './security-best-practices'; const authSecurity = new FirebaseAuthSecurity(); // Secure user registration async function registerUser(email, password, displayName) { try { await SecurityBestPractices.validateEmail(email); const userCredential = await authSecurity.secureSignUp(email, password, displayName); console.log('User registered securely:', userCredential.user.uid); } catch (error) { console.error('Registration failed:', error.message); } } // Secure file upload async function uploadFile(file, userId) { try { await SecurityBestPractices.validateFileUpload(file); // Proceed with Firebase Storage upload } catch (error) { console.error('File upload rejected:', error.message); } } ``` This comprehensive Firebase security implementation provides: - ✅ **Firestore Security Rules** with role-based access - ✅ **Storage Security Rules** with user isolation - ✅ **Authentication Security** with email verification and session management - ✅ **App Check** for API protection - ✅ **Security Monitoring** with audit trails - ✅ **Input Validation** and sanitization - ✅ **File Upload Security** with type and size validation - ✅ **Security Headers** for web hosting The implementation follows Firebase security best practices and provides multiple layers of protection for your application. --- .devcontainer/Securely Fairbase | 58 +++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .devcontainer/Securely Fairbase diff --git a/.devcontainer/Securely Fairbase b/.devcontainer/Securely Fairbase new file mode 100644 index 0000000..b9cb84b --- /dev/null +++ b/.devcontainer/Securely Fairbase @@ -0,0 +1,58 @@ +I've looked into Firebase security for you. It's a crucial topic, as a misconfigured Firebase instance can lead to significant data leaks. Securing your app involves combining several layers of protection. + +For a quick overview, the table below summarizes the three key layers of Firebase security: + +| **Security Layer** | **Primary Function** | **Key Concepts** | +| :--- | :--- | :--- | +| **Firebase App Check** | Protects backend resources from abuse (e.g., fraudulent traffic, billing fraud). | Integrity of the app instance. | +| **Firebase Authentication** | Manages user identity and provides auth context for rules. | User UID (`auth.uid`), custom claims (`auth.token`). | +| **Firebase Security Rules** | Controls data access for Firestore, Realtime DB, and Cloud Storage. | Path matching, `allow` conditions, `request.auth` validation. | + +### 🛡️ A Closer Look at Security Rules + +Security Rules are a powerful, customizable language that stands between your data and malicious users. Their syntax differs slightly between products. + +- **For Cloud Firestore and Cloud Storage**, rules use a custom language with `match` and `allow` statements: + ```javascript + service cloud.firestore { + match /databases/{database}/documents { + // Match the resource path. + match /users/{userId} { + // Allow the request if the following conditions are true. + allow read, write: if request.auth != null && request.auth.uid == userId; + } + } + } + ``` +- **For Realtime Database**, rules are JSON-based: + ```json + { + "rules": { + "users": { + "$uid": { + ".write": "$uid === auth.uid" + } + } + } + } + ``` + +You can leverage the `auth` variable in your rules to control access based on user identity. For instance, you can ensure users can only read and write their own data by comparing the `auth.uid` variable with the user ID on the requested data. + +Beyond basic ownership, you can implement more complex patterns like **Role-Based Access Control (RBAC)**. By storing a user's role (e.g., 'admin', 'editor') in a custom token claim or a Firestore document, you can write rules that check this role before granting access. + +Rules can also **validate data** structure. You can enforce that specific fields are strings, numbers, or have a certain format before allowing a write operation. + +### 🔒 Your Security Implementation Pathway + +A robust implementation involves a structured process: + +1. **Set Up Authentication**: Begin by adding Firebase Authentication to your app, as it provides the user context (`auth.uid`) that is essential for most security rules. +2. **Structure Your Data Thoughtfully**: How you structure your data directly impacts how you write your rules. Plan your database hierarchy with security in mind. +3. **Write and Iterate on Rules**: Start with basic rules for your core use cases, like making a user's data accessible only to them. +4. **Test Thoroughly**: Use the **Firebase Local Emulator Suite** to test your app's behavior and validate your rules in a safe environment before deploying them to production. You can also use the simulator in the Firebase console for quick checks. +5. **Deploy with Confidence**: Once tested, deploy your rules to production using either the Firebase console or the Firebase CLI. + +Remember, a well-secured Firebase app uses **App Check, Authentication, and Security Rules together** to create a defense-in-depth strategy. + +I hope this gives you a solid foundation for securing your Firebase project. If you'd like to dive deeper into a specific product, like Firestore rules for a particular use case, feel free to ask.