Commit 83811f2
authored
Secure Fairbase
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, ''')
.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.1 parent e368991 commit 83811f2
1 file changed
+58
-0
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
0 commit comments