Authentication
The AI Ingredient Scanner uses Firebase Authentication for user management, with Google Sign-In as the primary authentication method.
Overview
Features
| Feature | Description |
|---|---|
| Google Sign-In | OAuth 2.0 authentication via Firebase |
| Guest Mode | Anonymous usage without account |
| Profile Sync | Firestore persistence across devices |
| Preferences Sync | Allergies, skin type, theme saved to cloud |
| Account Deletion | GDPR-compliant data removal |
Authentication Flow
1. Initial State
When the app launches, it checks for an existing Firebase session:
// AuthContext.tsx
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, async (firebaseUser) => {
setUser(firebaseUser);
if (firebaseUser) {
await syncUserToFirestore(firebaseUser);
}
setLoading(false);
});
return () => unsubscribe();
}, []);
2. Login Screen
Users see a premium login screen with two options:
- Continue with Google - Full authentication with profile sync
- Continue as Guest - Anonymous mode (no data persistence)
3. Google Sign-In
Platform-specific implementation:
const signInWithGoogle = async () => {
if (Platform.OS === 'web') {
// Web: Firebase popup
await signInWithPopup(auth, googleProvider);
} else {
// Native: Expo Auth Session
await promptAsync();
}
};
4. Firestore Profile Sync
On successful sign-in, user data syncs to Firestore:
// User document structure
interface FirestoreUser {
uid: string;
email: string | null;
displayName: string | null;
photoURL: string | null;
provider: string;
createdAt: Timestamp;
lastLoginAt: Timestamp;
preferences?: {
allergies: string[];
skinType: SkinType;
expertise: ExpertiseLevel;
theme: ThemeMode;
};
}
Preferences Sync
User preferences are managed by PreferencesContext and automatically synced:
Synced Preferences
| Preference | Description | Default |
|---|---|---|
allergies | Known allergens to flag | [] |
skinType | Skin type for cosmetic analysis | normal |
expertise | Explanation complexity | beginner |
theme | Light or dark mode | light |
Load on Login
When a user signs in, their preferences are loaded from Firestore:
// PreferencesContext.tsx
useEffect(() => {
if (user) {
const userRef = doc(db, 'users', user.uid);
const userSnap = await getDoc(userRef);
if (userSnap.exists()) {
const prefs = userSnap.data().preferences;
setPreferences(prefs);
setThemeMode(prefs.theme); // Sync with ThemeContext
}
}
}, [user]);
Auto-Save on Change
Preferences are saved with debouncing (1 second delay) to prevent excessive writes:
const debouncedSave = useCallback((newPrefs: UserPreferences) => {
if (saveTimeoutRef.current) {
clearTimeout(saveTimeoutRef.current);
}
saveTimeoutRef.current = setTimeout(async () => {
if (user) {
await setDoc(doc(db, 'users', user.uid),
{ preferences: newPrefs },
{ merge: true }
);
}
}, 1000);
}, [user]);
Firebase Configuration
Setup
Firebase is configured in src/config/firebase.ts:
const firebaseConfig = {
apiKey: 'your-api-key',
authDomain: 'your-project.firebaseapp.com',
projectId: 'your-project-id',
storageBucket: 'your-project.appspot.com',
messagingSenderId: 'your-sender-id',
appId: 'your-app-id',
measurementId: 'your-measurement-id',
};
OAuth Configuration
For Google Sign-In to work:
- Firebase Console: Enable Google provider in Authentication
- Google Cloud Console: Configure OAuth consent screen
- Web Client ID: Add to
AuthContext.tsx
const [request, response, promptAsync] = Google.useAuthRequest({
webClientId: 'your-oauth-client-id.apps.googleusercontent.com',
});
User Management
Sign Out
const signOut = async () => {
await firebaseSignOut(auth);
setUserProfile(null);
};
Account Deletion
GDPR-compliant deletion removes all user data:
const deleteAccount = async () => {
// 1. Delete scan history subcollection
const scansRef = collection(db, 'users', user.uid, 'scans');
const scansSnap = await getDocs(scansRef);
const batch = writeBatch(db);
scansSnap.docs.forEach((scanDoc) => {
batch.delete(scanDoc.ref);
});
// 2. Delete user document
batch.delete(doc(db, 'users', user.uid));
await batch.commit();
// 3. Delete Firebase auth user
await deleteUser(user);
};
Firestore Security Rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Users can only access their own data
match /users/{userId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
// Scan history subcollection
match /scans/{scanId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
}
}
}
UI Components
Login Screen
Premium gradient design with:
- App branding and logo
- Feature pills (Scan Labels, AI Analysis, Allergy Alerts)
- Google Sign-In button
- Guest mode option
- Privacy policy link
ProfileAvatar
Displays user's profile picture or a colored initial fallback:
<ProfileAvatar
user={user}
size={48}
onPress={() => setCurrentScreen('profile')}
/>
Behavior:
- With Google photo: Shows the profile picture from
user.photoURL - Without photo: Shows first initial of display name with colored background
- Guest mode: Shows "G" with colored background
The background color is deterministically generated from the user's UID for consistency across sessions.
Profile Section (ProfileSelector)
In Settings, authenticated users see:
- ProfileAvatar: Google photo or colored initial fallback
- Display name and email address
- Sign Out button
- Privacy Policy (in-app modal via
PrivacyPolicyModal) - Collapsible Danger Zone with Delete Account option
Guest users see:
- Guest mode avatar with "G" initial
- Sign In with Google button
- Privacy Policy link
Danger Zone Pattern
Account deletion is protected by a collapsible section to prevent accidental clicks:
const [showDangerZone, setShowDangerZone] = useState(false);
// Toggle with smooth animation
const toggleDangerZone = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setShowDangerZone(!showDangerZone);
};
The Danger Zone is collapsed by default. Users must tap to expand before seeing the Delete Account button.
PrivacyPolicyModal
In-app modal that displays the privacy policy without leaving the app:
<PrivacyPolicyModal
visible={showPrivacy}
onClose={() => setShowPrivacy(false)}
/>
Benefits:
- Works offline (no external URL required)
- Keeps users in the app
- Theme-aware styling
Privacy
The privacy policy is displayed in-app via PrivacyPolicyModal. It covers:
- Data collection and usage
- User rights (access, update, delete)
- Third-party services (Firebase, Google)
- Data retention policies