Skip to content

useSimpleAuth Hook

A simplified authentication hook for Firebase auth state monitoring and Multi-Factor Authentication (MFA) detection.

Overview

useSimpleAuth is a focused authentication hook designed specifically for sign-in pages that need to detect and handle MFA requirements. It monitors Firebase authentication state changes and provides utilities to detect when Firebase requests Multi-Factor Authentication during sign-in.

What it handles:

  • Firebase authentication state monitoring via onAuthStateChanged
  • MFA requirement detection from Firebase auth errors
  • Auth status tracking for UI rendering decisions
  • MFA resolver management for verification components

What it does NOT handle:

  • Backend user verification or profile loading
  • Company selection after authentication
  • Complete auth flow orchestration

The hook is called “Simple” because it’s a subset of a planned full useAuth hook. It focuses solely on Firebase auth state and MFA detection, making it perfect for sign-in pages without the overhead of complete authentication management.

Responsibilities

The hook has four core responsibilities:

  1. Monitors Firebase authentication state - Subscribes to Firebase onAuthStateChanged to track when users sign in, sign out, or their session changes
  2. Detects MFA requirements - Identifies when Firebase throws an MFA-required error during sign-in attempts
  3. Provides auth status for UI rendering - Maintains current authentication status to help components decide what to render
  4. Tracks MFA resolver - Stores the Firebase MultiFactorResolver needed by MFA verification components

State Management

AUTH_STATUS Constants

The hook uses five status constants to represent the authentication state:

StatusValueDescriptionWhen Set
INITIALIZING_FIREBASE_SESSION'initializing.firebase_session'Firebase auth state is being determinedOn hook mount, before Firebase callback
UNAUTHENTICATED'unauthenticated'No user is signed inFirebase onAuthStateChanged returns null
AUTHENTICATED'authenticated'User successfully signed in (normal flow)Firebase onAuthStateChanged returns user object
AUTHENTICATED_VIA_MFA'authenticated.via_mfa'User completed MFA and is now authenticatedFirebase onAuthStateChanged returns user after MFA_REQUIRED state
MFA_REQUIRED'mfa.required'MFA verification neededdetectMfaRequest() detects auth/multi-factor-auth-required error

AuthState Type

The internal state shape managed by the hook:

type AuthState = {
status: AuthStatus,
user: ?firebase.User,
mfaResolver: ?MultiFactorResolver,
}

Field population:

  • status - Always set, starts as INITIALIZING_FIREBASE_SESSION
  • user - Populated when status is AUTHENTICATED or AUTHENTICATED_VIA_MFA, null otherwise
  • mfaResolver - Only populated when status is MFA_REQUIRED, null after authentication or on sign-out

API Reference

Return Values

const {
// State (read-only)
status,
user,
mfaResolver,
// Derived boolean helpers
isInitializing,
isUnauthenticated,
isAuthenticated,
mfaRequired,
// Utility functions
detectMfaRequest,
} = useSimpleAuth();

State Properties

PropertyTypeDescription
statusAuthStatusCurrent authentication status (one of five AUTH_STATUS constants)
user?firebase.UserFirebase user object when authenticated, null otherwise
mfaResolver?MultiFactorResolverFirebase MFA resolver when MFA is required, null otherwise

Derived Helpers

Boolean helpers for common status checks:

HelperTypeDescription
isInitializingbooleantrue when status is INITIALIZING_FIREBASE_SESSION
isUnauthenticatedbooleantrue when status is UNAUTHENTICATED
isAuthenticatedbooleantrue when status is AUTHENTICATED or AUTHENTICATED_VIA_MFA
mfaRequiredbooleantrue when status is MFA_REQUIRED

Utility Functions

detectMfaRequest(error: Error): boolean

Detects if a Firebase error indicates MFA is required.

  • Parameters: Firebase error object from sign-in attempt
  • Returns: true if MFA was detected, false otherwise
  • Side Effects: If MFA detected, updates state to MFA_REQUIRED and stores the resolver
  • Usage: Call in the catch block of sign-in attempts

Usage Examples

Basic Sign-in with MFA Detection

import useSimpleAuth from '../../hooks/auth/useSimpleAuth';
import firebase from 'firebase/app';
function SignInPage() {
const { detectMfaRequest, mfaRequired, mfaResolver } = useSimpleAuth();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSignIn = async () => {
try {
await firebase.auth().signInWithEmailAndPassword(email, password);
// Success - Firebase will trigger onAuthStateChanged
// which updates the hook state to AUTHENTICATED
} catch (error) {
// Check if this is an MFA request
if (detectMfaRequest(error)) {
// Hook state automatically updated to MFA_REQUIRED
// UI will re-render to show MFA form
return;
}
// Handle other errors (wrong password, user not found, etc.)
console.error('Sign-in error:', error.message);
}
};
return (
<>
{!mfaRequired && <SignInForm onSubmit={handleSignIn} />}
{mfaRequired && <MfaVerificationForm mfaResolver={mfaResolver} />}
</>
);
}

Conditional Rendering Based on Status

import { AUTH_STATUS } from '../../hooks/auth/auth';
import useSimpleAuth from '../../hooks/auth/useSimpleAuth';
function AuthPage() {
const { status, isAuthenticated, mfaRequired } = useSimpleAuth();
// Show loading while Firebase initializes
if (status === AUTH_STATUS.INITIALIZING_FIREBASE_SESSION) {
return <LoadingSpinner />;
}
// Show MFA verification form
if (mfaRequired) {
return <MfaVerificationForm />;
}
// User is authenticated, show next step
if (isAuthenticated) {
return <CompanySelection />;
}
// Default: show sign-in form
return <SignInForm />;
}

Integration Patterns

Sign-in Page Integration

The typical integration pattern for sign-in pages:

  1. Import the hook at the top of your component
  2. Call detectMfaRequest() in the catch block of sign-in attempts
  3. Conditionally render MFA UI based on the mfaRequired boolean
  4. Pass mfaResolver to the MFA verification component

The hook automatically manages state transitions, so you only need to react to the current state.

State Transitions

The hook handles these state transitions automatically:

INITIALIZING_FIREBASE_SESSION
├─→ UNAUTHENTICATED (Firebase returns no user)
└─→ AUTHENTICATED (Firebase returns existing user session)
UNAUTHENTICATED
├─→ AUTHENTICATED (successful sign-in)
└─→ MFA_REQUIRED (detectMfaRequest() called with MFA error)
MFA_REQUIRED
└─→ AUTHENTICATED_VIA_MFA (user completes MFA verification)
AUTHENTICATED or AUTHENTICATED_VIA_MFA
└─→ UNAUTHENTICATED (user signs out)

Key insight: The distinction between AUTHENTICATED and AUTHENTICATED_VIA_MFA helps track whether the current session came through MFA, which can be useful for analytics or security auditing.

Relationship with useMfa

useSimpleAuth and useMfa work together but have distinct responsibilities:

HookResponsibilityFocus
useSimpleAuthDetection & State ManagementMonitors Firebase auth state, detects MFA requirements, provides status
useMfaMFA Verification LogicHandles reCAPTCHA, SMS sending, code verification, error mapping

Why separate?

  • Single Responsibility: Each hook has one clear purpose
  • Reusability: useMfa can be used independently in different contexts
  • Testability: Easier to test each layer separately

Data Flow:

  1. useSimpleAuth detects MFA requirement (via detectMfaRequest())
  2. Provides mfaResolver to components
  3. useMfa (inside MfaPhoneVerification) uses the resolver for verification
  4. On success, Firebase triggers onAuthStateChanged
  5. useSimpleAuth updates to AUTHENTICATED_VIA_MFA

Usage in Codebase

The hook is currently integrated in these sign-in flows:

  • /views/SignIn/index.js - Password-based sign-in with MFA detection
  • /pages/sign-in-confirmation.js - Email link sign-in with MFA support

Both implementations follow the same pattern: catch Firebase errors, call detectMfaRequest(), and conditionally render MFA UI.

  • MFA Documentation - Complete MFA implementation details including components and flows
  • hooks/auth/README.md - Quick reference for all authentication hooks