React Native Authentication Rescue:
Tennis Management App

How we eliminated infinite login loops, resolved Firebase integration failures, and fixed iOS App Store rejection 5.1.1 for a React Native mobile app in 6 weeks.

Client: Client N Duration: 6 weeks Tech: React Native, Firebase, TypeScript Outcome: 93% cost savings
6
Week Delivery
100%
Auth Issues Resolved
93%
Cost Savings
0
Production Blockers

Executive Summary

Project: Tennis Management App - Mobile Application

Role: Technical Lead / Rescue Architecture Engineer

Duration: Phase 1 (30 December 2025 - 4 February 2026)

Stack: React Native (Expo), Firebase (Firestore/Auth), TypeScript, EAS Build, Google Play Console

Challenge Overview

Inherited a distressed codebase suffering from critical architectural failures that blocked production deployment:

Mission-Critical Outcomes

  1. Authentication Architecture Stabilization - Eliminated infinite loops, race conditions, and platform-specific state persistence failures
  2. Database Integration Remediation - Resolved systematic write failures, fixed document ID validation, corrected database configuration
  3. Production Deployment Pipeline - Successfully generated production .aab for Google Play Store, navigated new developer account testing requirements
  4. App Store Compliance & iOS Native Configuration - Resolved native permission rejections (Guideline 5.1.1) and engineered "fail-open" role switching logic to satisfy App Store reviewer login flows without compromising database integrity

Result: Fixed critical authentication issues that completely blocked user access, enabling the client to launch their business. Alternative agency quotes ranged from £18,000-£35,000 for similar work. We delivered in 6 weeks for a fraction of that cost.


1. Technical Archaeology: Inherited System State

1.1 Authentication Infrastructure Failures

Primary Symptom: User accounts trapped in infinite redirect loops on login.

Root Cause Analysis:

// FAILURE PATTERN: Race condition between auth initialization and routing guards
// Location: AuthProvider.tsx + navigation guards

// Problem 1: AsyncStorage mock profile loading asynchronously
const mockProfile = await AsyncStorage.getItem('@strung/mock_profile');

// Problem 2: Navigation guards executing before profile resolution
if (!isAuthenticated) navigate('/login'); // Executes before mock loads

// Problem 3: isAuthenticated evaluation inconsistency
const isAuthenticated = !!(user || mockProfile); // Timing dependent

Architectural Deficiency:

Impact Severity:

1.2 Database Integration Catastrophe

Primary Symptom: All write operations failing silently while read operations succeeded normally.

Diagnostic Timeline:

  1. Initial Hypothesis: Network connectivity/proxy issues → Ruled out via successful reads
  2. Secondary Hypothesis: Security rules misconfiguration → Rules validated as permissive
  3. Actual Root Cause: Multi-layered configuration mismatches

Critical Issues Discovered:

// ISSUE 1: Database Name Configuration
// firebase.ts configuration
const db = getFirestore(app);
// ❌ No explicit database name = defaulted to "(default)"
// ✅ Required: Explicit database name from console

// ISSUE 2: Invalid Document ID Characters
await setDoc(doc(db, 'users', stringerId, 'customers', customer.email));
// ❌ Document IDs containing forbidden characters: '@', '.', '/'
// ✅ Required: Sanitized IDs using timestamps or UUID format

// ISSUE 3: Platform App ID Misconfiguration
// android/app/build.gradle
applicationId "com.wrong.package"
// ❌ Mismatched with Firebase project registration
// ✅ Required: Exact match with console configuration

1.3 The "Portal Loop" & Role Switching Deadlock

Problem Statement: Users (specifically App Store reviewers and dual-role users) were trapped in an infinite routing loop when attempting to switch between different app sections.

// FLAWED LOGIC: Prioritizing Database History over User Intent
const ensureProfileDocument = async ({ user, expectedRole }) => {
  const storedRole = snapshot.data()?.role;
  // ❌ The app forced the user back to their 'stored' role, ignoring their login choice
  const resolvedRole = storedRole ?? expectedRole ?? 'default'; 
};

Impact Severity:


2. Critical Rescue Phase: Architecture Stabilization

2.1 Authentication State Machine Redesign

Objective: Eliminate race conditions, guarantee deterministic initialization, support multiple authentication flows.

Solution Architecture:

// NEW STATE MACHINE: Explicit initialization lifecycle
type AuthState = 
  | { phase: 'initializing'; user: null; profile: null }
  | { phase: 'unauthenticated'; user: null; profile: null }
  | { phase: 'authenticated'; user: User; profile: Profile }
  | { phase: 'mock'; user: null; profile: MockProfile };

// SYNCHRONOUS MOCK LOADING: Prevent routing before profile resolution
useEffect(() => {
  let mounted = true;
  const initialize = async () => {
    setIsInitializing(true);
    
    // Load mock profile BEFORE setting initialized state
    const mockData = await AsyncStorage.getItem('@strung/mock_profile');
    if (!mounted) return;
    
    if (mockData) {
      const profile = JSON.parse(mockData);
      setMockProfile(profile);
      setIsAuthenticated(true); // Only set after profile loaded
    }
    
    setIsInitializing(false); // Signal: safe to evaluate routing
  };
  
  initialize();
  return () => { mounted = false; };
}, []);

Key Improvements:

  1. Deterministic Initialization: isInitializing flag prevents premature route evaluation
  2. Synchronous Mock Loading: Profile guaranteed available before isAuthenticated = true
  3. Single Source of Truth: Eliminated conflicting state across providers
  4. Graceful Degradation: Clear separation between authentication flows

2.1.B Session-Based Role Switching (The "Fail-Open" Strategy)

Challenge: Database security rules prevented users from updating their own role field, causing the app to crash when trying to switch between app sections.

Architectural Solution: "Session Priority" Pattern

We implemented a fail-open strategy that prioritizes the requested session role over the stored database role without requiring a database write to succeed.

// SOLUTION: Inverted Priority & Fail-Open Write
const ensureProfileDocument = async ({ user, expectedRole }) => {
  // 1. Prioritize User Intent (Session Scope)
  const resolvedRole = expectedRole ?? storedRole ?? 'default';

  try {
    // 2. Attempt to update DB (Best Effort)
    await setDoc(profileRef, { role: resolvedRole }, { merge: true });
  } catch (error) {
    // 3. Fail-Open Handler
    if (error.code === 'permission-denied') {
      console.warn('[AuthProvider] DB Role Lock. Proceeding with Session-Only profile.');
      // ⚠️ CRITICAL: We return the 'resolvedRole' to the app state, 
      // allowing the session to proceed even if the DB write failed.
    }
  }

  // 4. Return Session Profile (Virtual)
  return { ...profileData, role: resolvedRole };
};

Benefits:

2.2 Database Integration Remediation

Systematic Configuration Audit Performed:

Configuration Point Issue Found Resolution
Database name Using default "(default)" Explicit databaseId: "default"
Document ID validation Using email addresses directly Switched to Date.now().toString()
Android applicationId Mismatched with console Updated to com.strung.app
iOS bundleIdentifier Legacy identifier from fork Updated to com.strung.ios
EAS Build configuration Missing credentials Ran eas credentials setup

Validation Testing Protocol: After configuration changes, we implemented systematic validation tests for writes, reads, and real-time listeners to confirm full functionality across all collections and platforms.

2.3 iOS App Store Compliance Engineering

Challenge: Apple rejected build due to generic permission strings (Guideline 5.1.1), blocking release.

Apple's Rejection Details:

Guideline 5.1.1 - Legal - Privacy - Data Collection and Storage

We noticed your app requests the user's consent to access their camera and photos but does not sufficiently explain the use of the camera and photos in the purpose string.

Root Cause Analysis:

Solution:

// app.json configuration
"ios": {
  "infoPlist": {
    "NSCameraUsageDescription": "App needs access to your camera to scan QR codes for jobs and equipment.",
    "NSPhotoLibraryUsageDescription": "App needs access to your photos to let you upload profile pictures and equipment images.",
    "NSMicrophoneUsageDescription": "App needs access to your microphone to record audio when capturing videos of equipment."
  }
}

Results:


3. Key Technical Innovations

3.1 Systematic Debugging Methodology

We developed a repeatable debugging approach for complex multi-platform issues:

  1. Isolation Testing:
    • Separate authentication from database operations
    • Test reads independently from writes
    • Validate security rules in isolation
    • Platform-specific testing (Android/iOS/Web)
  2. Configuration Audit Checklist:
    • Verify database name in console
    • Check databaseId parameter
    • Validate applicationId matches
    • Confirm bundleIdentifier is correct
    • Ensure document IDs have no forbidden characters
  3. Comprehensive Logging:
    console.log('[Component] Action', {
      collection: 'customers',
      documentId: customer.id,
      timestamp: new Date().toISOString(),
    });
  4. Version Control Discipline:
    • Commit after each configuration change
    • Tag successful test runs
    • Document all changes in commit messages

3.2 Provider Context Optimization

Problem: Multiple context providers re-rendering entire app on every state change.

Solution: Granular State Updates with Refs

// BEFORE: Every update triggers full context re-render
const DataProvider = ({ children }) => {
  const [customers, setCustomers] = useState([]);
  return (
    
      {children}
    
  );
};

// AFTER: Refs prevent unnecessary re-renders
const DataProvider = ({ children }) => {
  const [customers, setCustomers] = useState([]);
  const customersRef = useRef([]);
  
  const updateCustomersState = useCallback((next) => {
    customersRef.current = next;
    setCustomers(next);
  }, []);
  
  // Only update if data actually changed
  if (hasChanges) {
    updateCustomersState(fetched);
  }
};

Performance Gains:


4. Lessons Learned & Best Practices

4.1 Database Integration Pitfalls

Lesson 1: Configuration Mismatches Are Silent Killers

Lesson 2: Document ID Validation Is Non-Obvious

4.2 State Management Complexity

Lesson 3: Multiple Providers Create Dependency Hell

Lesson 4: Real-Time Listeners Need Change Detection

4.3 Role Switching & App Store Compliance

Lesson 5: Database-First Role Resolution Breaks Reviewer Flows

Lesson 6: Native Permission Strings Freeze at Build Time


5. Business Impact & ROI

5.1 Client Cost Savings

Phase 1 Rescue Value:

5.2 Technical Debt Elimination

Quantified Improvements:

Maintainability Gains:


6. Conclusion

This project exemplifies the critical importance of systematic debugging, architectural discipline, and enterprise-grade engineering practices in mobile application development.

By methodically addressing authentication race conditions, database configuration mismatches, and App Store compliance challenges, the application progressed from a non-functional state to production-ready deployment in just 6 weeks.

Key Achievements:

Technical Excellence Demonstrated:

Business Value Delivered:

Project Status: Phase 1 Complete ✅ | Production Deployment Ready

Technology Stack: React Native (Expo) | TypeScript | Firebase (Firestore/Auth/Storage) | EAS Build | Google Play Console

About the Author

Shaquell Lakhal-Davis is the Founder & Technical Director at SLD Digital, a boutique software consultancy specializing in mobile app rescue and architecture recovery.

With 10 years of experience in full-stack development, Shaquell has led technical recovery for multiple mission-critical applications facing production-blocking failures. He specializes in React Native, TypeScript, and Firebase architecture, with a focus on systematic debugging and performance optimization.

SLD Digital partners with CTOs and technical founders who need rapid, systematic resolution of complex mobile architecture challenges.

Discuss Your Project →