Skip to main content

Overview

Argument Cartographer uses Firebase for authentication and data persistence. This guide walks you through setting up Firebase with the strict security model implemented in the application.

Create Firebase Project

1

Create New Project

  1. Go to Firebase Console
  2. Click “Add project”
  3. Enter project name (e.g., “argument-cartographer”)
  4. Enable Google Analytics (optional)
  5. Click “Create project”
2

Register Web App

  1. In Firebase Console, click “Add app”Web
  2. Register app nickname (e.g., “Argument Cartographer Web”)
  3. Check “Also set up Firebase Hosting” (optional)
  4. Click “Register app”
  5. Copy the Firebase configuration values
3

Get Configuration Values

Copy these values to your .env file:
const firebaseConfig = {
  apiKey: "AIzaSy...",           // → NEXT_PUBLIC_FIREBASE_API_KEY
  authDomain: "project.firebaseapp.com",  // → NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN
  projectId: "your-project-id",   // → NEXT_PUBLIC_FIREBASE_PROJECT_ID
  storageBucket: "project.appspot.com",   // → NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET
  messagingSenderId: "123456",    // → NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID
  appId: "1:123:web:abc",         // → NEXT_PUBLIC_FIREBASE_APP_ID
};

Configure Firebase Authentication

1

Enable Authentication

In Firebase Console:
  1. Go to BuildAuthentication
  2. Click “Get started”
2

Enable Sign-in Methods

Enable the authentication providers you want to support:Recommended providers:
  • Email/Password: Basic authentication
  • Google: OAuth sign-in
  • Anonymous: Guest access (optional)
The application’s security rules work with any Firebase authentication provider.
3

Configure Authorized Domains

Add your domains to authorized list:
  1. Go to AuthenticationSettingsAuthorized domains
  2. Add your domains:
    • localhost (for development)
    • Your Vercel domain (e.g., your-app.vercel.app)
    • Custom domain (if applicable)

Set Up Firestore Database

1

Create Firestore Database

  1. Go to BuildFirestore Database
  2. Click “Create database”
  3. Select location (choose closest to your users)
  4. Start in Production mode (we’ll add security rules next)
2

Deploy Security Rules

The application uses a strict user-ownership model. Deploy these security rules:
The security rules enforce that all data is private to individual users. Never modify the ownership checks without careful review.
Navigate to Firestore DatabaseRules and paste the following:
firestore.rules
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    
    // Helper Functions
    function isSignedIn() {
      return request.auth != null;
    }
    
    function isOwner(userId) {
      return isSignedIn() && request.auth.uid == userId;
    }
    
    function isExistingOwner(userId) {
      return isOwner(userId) && resource != null;
    }
    
    function isCreatingOwnProfile(userId) {
      return request.resource.data.id == userId;
    }
    
    function isCreatingOwnSubcollectionDoc(userId) {
      return request.resource.data.userId == userId;
    }
    
    function isUserIdImmutable() {
      return request.resource.data.id == resource.data.id;
    }
    
    function isOwnerIdImmutable() {
      return request.resource.data.userId == resource.data.userId;
    }
    
    // User Profiles
    match /users/{userId} {
      allow get: if isOwner(userId);
      allow list: if false;  // Prevent scraping
      allow create: if isOwner(userId) && isCreatingOwnProfile(userId);
      allow update: if isExistingOwner(userId) && isUserIdImmutable();
      allow delete: if isExistingOwner(userId);
    }
    
    // Argument Maps
    match /users/{userId}/argumentMaps/{argumentMapId} {
      allow get: if isOwner(userId);
      allow list: if isOwner(userId);
      allow create: if isOwner(userId) && isCreatingOwnSubcollectionDoc(userId);
      allow update: if isExistingOwner(userId) && isOwnerIdImmutable();
      allow delete: if isExistingOwner(userId);
    }
    
    // Sources
    match /users/{userId}/sources/{sourceId} {
      allow get: if isOwner(userId);
      allow list: if isOwner(userId);
      allow create: if isOwner(userId) && isCreatingOwnSubcollectionDoc(userId);
      allow update: if isExistingOwner(userId) && isOwnerIdImmutable();
      allow delete: if isExistingOwner(userId);
    }
  }
}
Click “Publish” to deploy the rules.
3

Verify Security Rules

Test the rules using Firebase’s Rules Playground:
  1. Go to Rules tab → Rules Playground
  2. Test scenarios:
    • Authenticated user reading own data: ✅ Allow
    • Authenticated user reading other user’s data: ❌ Deny
    • Unauthenticated read: ❌ Deny
    • List all users: ❌ Deny (privacy protection)

Security Model Deep Dive

Core Philosophy

The application implements a strict user-ownership model based on these principles:
All operations are denied unless explicitly allowed. There is no public data.
Ownership is determined by the {userId} wildcard in the document path, avoiding costly database reads:
/users/{userId}/argumentMaps/{mapId}

        └─ Must match request.auth.uid
Documents include a userId field that’s validated on creation and immutable:
function isCreatingOwnSubcollectionDoc(userId) {
  return request.resource.data.userId == userId;
}
Listing the /users collection is explicitly forbidden:
match /users/{userId} {
  allow list: if false;  // Protect user privacy
}

Data Structure

All user data is organized hierarchically:
/users/{userId}                          ← User profile
├── /argumentMaps/{mapId}                ← Analysis results
│   ├── blueprint: ArgumentNode[]        ← Structured arguments
│   ├── summary: string                  ← Analysis summary
│   ├── credibilityScore: number         ← Quality rating
│   └── fallacies: DetectedFallacy[]     ← Logical fallacies
└── /sources/{sourceId}                  ← Scraped sources
    ├── url: string                      ← Source URL
    └── content: string                  ← Extracted text

Permission Error Handling

The application includes custom error handling for permission denials:
// From firebase/errors.ts
export class FirestorePermissionError extends Error {
  public readonly request: SecurityRuleRequest;
  
  constructor(context: SecurityRuleContext) {
    const requestObject = buildRequestObject(context);
    super(buildErrorMessage(requestObject));
    this.name = 'FirebaseError';
    this.request = requestObject;
  }
}
This provides detailed debugging information when security rules reject requests.

Generate Service Account Key

For server-side operations (admin SDK), generate a service account key:
1

Navigate to Service Accounts

In Firebase Console:
  1. Go to Project Settings (gear icon)
  2. Select Service accounts tab
2

Generate Private Key

  1. Click “Generate new private key”
  2. Click “Generate key” in the confirmation dialog
  3. Save the JSON file securely (never commit to version control)
3

Extract Values for Environment Variables

Open the downloaded JSON file and extract:
service-account.json
{
  "project_id": "your-project-id",           // → SERVICE_ACCOUNT_PROJECT_ID
  "private_key": "-----BEGIN PRIVATE KEY-----\n...",  // → SERVICE_ACCOUNT_PRIVATE_KEY
  "client_email": "firebase-adminsdk@..."    // → SERVICE_ACCOUNT_CLIENT_EMAIL
}
When adding SERVICE_ACCOUNT_PRIVATE_KEY to Vercel:
  1. Include the entire key with quotes
  2. Newlines should be \n (escaped)
  3. Format: "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"

Configure Firestore Indexes

For optimal query performance, create composite indexes:
1

Navigate to Indexes

Go to Firestore DatabaseIndexes
2

Add Composite Indexes

Create indexes for common query patterns:Argument Maps by User and Date:
  • Collection: argumentMaps
  • Fields:
    • userId (Ascending)
    • createdAt (Descending)
  • Query scope: Collection
Firebase will automatically prompt you to create indexes when it detects missing ones in your queries.

Testing Firebase Integration

Verify your Firebase setup:
import { initializeApp } from 'firebase/app';
import { getAuth, signInAnonymously } from 'firebase/auth';
import { getFirestore, doc, setDoc, getDoc } from 'firebase/firestore';

// Test authentication
const auth = getAuth();
const userCredential = await signInAnonymously(auth);
console.log('✅ Authentication successful:', userCredential.user.uid);

// Test Firestore write
const db = getFirestore();
const testDoc = doc(db, `users/${userCredential.user.uid}/test/doc1`);
await setDoc(testDoc, { test: true, userId: userCredential.user.uid });
console.log('✅ Firestore write successful');

// Test Firestore read
const snapshot = await getDoc(testDoc);
console.log('✅ Firestore read successful:', snapshot.data());

Monitoring and Maintenance

Firebase ConsoleUsage and billingTrack:
  • Firestore reads/writes/deletes
  • Authentication sign-ins
  • Storage usage
  • Network egress
Firebase free tier includes:
  • 50K reads/day
  • 20K writes/day
  • 1GB storage
Automated backups:
  1. Go to Firestore DatabaseDataBackups
  2. Enable Automatic backups
  3. Configure retention period (7-365 days)
Manual exports:
gcloud firestore export gs://your-bucket/backups/$(date +%Y%m%d)
Regularly review:
  • AuthenticationUsers - Check for suspicious accounts
  • FirestoreRules - Review rule changes
  • UsageLogs - Monitor denied requests
Set up alerts for:
  • Unusual spike in denied requests (possible attack)
  • High authentication failures
  • Quota approaching limits

Upgrade to Blaze Plan

For production deployments, consider upgrading to the Blaze (pay-as-you-go) plan: Benefits:
  • Increased quotas for high-traffic apps
  • Access to Firebase Extensions
  • Cloud Functions integration
  • Better SLA and support
When to upgrade:
  • Exceeding free tier limits
  • Need cloud functions for background processing
  • Production app with revenue
Set up billing alerts to avoid unexpected charges. Each argument analysis makes multiple API calls and Firestore operations.

Troubleshooting

Symptoms: Missing or insufficient permissions errorSolutions:
  1. Verify user is authenticated (request.auth != null)
  2. Check userId in path matches request.auth.uid
  3. Ensure document has required userId field
  4. Review security rules deployment status
Use the custom error handler for detailed debugging:
// The app logs the full request context on permission errors
console.error(error.request); // Contains auth, path, method
Check:
  • Private key format (escaped newlines: \n)
  • Key is wrapped in quotes
  • Client email is correct
  • Project ID matches
  • Service account has required permissions
Test with:
import admin from 'firebase-admin';
admin.initializeApp({
  credential: admin.credential.cert({
    projectId: process.env.SERVICE_ACCOUNT_PROJECT_ID,
    clientEmail: process.env.SERVICE_ACCOUNT_CLIENT_EMAIL,
    privateKey: process.env.SERVICE_ACCOUNT_PRIVATE_KEY?.replace(/\\n/g, '\n'),
  }),
});
Symptoms: Query fails with “index required” errorSolution:
  1. Click the link in the error message
  2. Firebase will create the index automatically
  3. Wait 2-5 minutes for index to build
  4. Retry the query

Next Steps

Environment Variables

Configure all required environment variables

Deploy to Vercel

Deploy your application to production

Common Issues

Troubleshoot deployment problems

Performance

Optimize Firebase performance

Build docs developers (and LLMs) love