newOS for Developers
Firebase Phone Auth
Phone number authentication via Firebase, including OTP verification and reCAPTCHA handling.
packages/newgraph-signals/src/actions/firebase.ts (164 lines)
The Firebase module contains the reference implementation for phone authentication. The actual production implementation may be handled at the app level or via Firebase-specific app configuration. This documentation covers the intended flow.
Quick Reference
Authentication Flow
initializeRecaptcha— Setup reCAPTCHArequestPhoneAuthCode— Send OTP via SMSsubmitPhoneVerificationCodeToFirebase— Verify OTPrefreshFirebaseToken— Force refresh ID token
Reactive Signals
auth— Firebase Auth instanceconfirmationResult— OTP confirmation handlerecaptchaVerifier— reCAPTCHA verifierfirebaseToken— Current Firebase ID token
Utilities
getRecaptchaVerifier— Get/create verifierclearRecaptchaVerifier— Clean up verifierresetRecaptchaVerifier— Reset after errorlogoutFromFirebase— Sign out from Firebase
Firebase Config
- Stage:
eu-prod - App Check: reCAPTCHA v3
- Token auto-refresh: enabled
Phone Authentication Flow
The complete flow from phone number input to Newgraph JWT:
1. initializeRecaptcha()
└── Setup invisible reCAPTCHA on button element
2. requestPhoneAuthCode("+1234567890")
└── Firebase sends SMS with 6-digit OTP
└── Returns ConfirmationResult
3. submitPhoneVerificationCodeToFirebase("123456")
└── Verify OTP code
└── Get Firebase User object
└── Get Firebase ID token
4. signIn(firebaseIdToken) [from auth.ts]
└── Exchange for Newgraph JWT
└── Store sessioninitializeRecaptcha
SetupInitialize Firebase and set up the reCAPTCHA verifier for phone auth.
initRecaptchaVerifier(
containerOrId: string | HTMLElement = "sign-in-button"
): voidBehavior
- Clears any existing verifier via
clearRecaptchaVerifier() - Creates new
RecaptchaVerifierwith size: "invisible" - Attaches to specified container element
- Stores in
recaptchaVerifiersignal
HTML Setup
<!-- Button that triggers phone auth -->
<button id="sign-in-button">Send Code</button>
<!-- Or custom container -->
<div id="recaptcha-container"></div>requestPhoneAuthCode
AsyncSend OTP verification code via SMS to the provided phone number.
requestPhoneAuthCode(phone: string): Promise<ConfirmationResult>| Parameter | Type | Description |
|---|---|---|
| phone | string | Phone number in E.164 format (e.g., +14155551234) |
Implementation
import { signInWithPhoneNumber, getAuth } from "firebase/auth";
confirmationResult.value = await signInWithPhoneNumber(
getAuth(),
phone,
getRecaptchaVerifier()
);Returns
ConfirmationResult object with .confirm(code) method. Also stored in confirmationResult signal.
submitPhoneVerificationCodeToFirebase
AsyncVerify the OTP code entered by the user and get the Firebase ID token.
submitPhoneVerificationCodeToFirebase(
phoneCode: string
): Promise<string | undefined>| Parameter | Type | Description |
|---|---|---|
| phoneCode | string | 6-digit OTP code from SMS |
Implementation
const userCreds = await confirmationResult.value?.confirm(phoneCode);
const token = await userCreds?.user.getIdToken(true);
firebaseToken.value = token;
return token;Returns
Firebase ID token string. This token should be passed tosignIn() from auth.ts to exchange for a Newgraph JWT.
Nuances
- Uses
getIdToken(true)to force fresh token - Updates
firebaseTokensignal for reactive use - Returns undefined if confirmation fails
refreshFirebaseToken
AsyncForce refresh the Firebase ID token for the current user.
refreshFirebaseToken(): Promise<string>Implementation
const auth = getAuth();
const idToken = await auth.currentUser.getIdToken({ forceRefresh: true });
return idToken;Use Case
Use this when you need to re-authenticate with Newgraph (e.g., token expired) without requiring the user to re-enter their phone number and OTP.
reCAPTCHA Management
getRecaptchaVerifier
getRecaptchaVerifier(
containerOrId: string | HTMLElement = "sign-in-button"
): RecaptchaVerifierCreates invisible reCAPTCHA verifier attached to specified element.
clearRecaptchaVerifier
clearRecaptchaVerifier(): voidClears existing verifier. Call before creating a new one or on logout.
resetRecaptchaVerifier
resetRecaptchaVerifier(name: string = "recaptcha-container"): voidResets the reCAPTCHA after an error. Fixes common issues like "Cannot read property 'style' of null" after failed verification.
- After image selector CAPTCHA challenge
- After expired CAPTCHA
- After network error during verification
Reactive Signals
Preact Signals for Firebase state management.
authSignalauth: Signal<Auth | null>Firebase Auth instance, initialized on module load.
confirmationResultSignalconfirmationResult: Signal<ConfirmationResult | null>Handle for confirming OTP. Use .confirm(code) to verify.
recaptchaVerifierSignalrecaptchaVerifier: Signal<RecaptchaVerifier | null>The reCAPTCHA verifier instance. Must be cleared on logout.
firebaseTokenSignalfirebaseToken: Signal<string | undefined>Current Firebase ID token after successful verification.
logoutFromFirebase
AsyncSign out from Firebase Auth.
logoutFromFirebase(): Promise<void>Behavior
- Calls
auth.value?.signOut() - Should be called alongside
signOut()from auth.ts - Clears Firebase session state
Complete Flow Example
import {
initRecaptchaVerifier,
requestPhoneAuthCode,
submitPhoneVerificationCodeToFirebase
} from "newgraph-signals/actions/firebase";
import { signIn } from "newgraph-signals/actions/auth";
// Step 1: Setup reCAPTCHA on component mount
useEffect(() => {
initRecaptchaVerifier("sign-in-button");
}, []);
// Step 2: Send OTP when user submits phone
async function handleSendCode(phone: string) {
try {
await requestPhoneAuthCode(phone);
setShowCodeInput(true);
} catch (error) {
console.error("Failed to send code:", error);
resetRecaptchaVerifier();
}
}
// Step 3: Verify OTP and sign in
async function handleVerifyCode(code: string) {
const firebaseToken = await submitPhoneVerificationCodeToFirebase(code);
if (firebaseToken) {
// Step 4: Exchange for Newgraph session
const jwt = await signIn(firebaseToken);
// User is now authenticated!
}
}Firebase App Check
The module initializes Firebase App Check with reCAPTCHA v3 for added security.
import { initializeAppCheck, ReCaptchaV3Provider } from "firebase/app-check";
const appCheck = initializeAppCheck(app, {
provider: new ReCaptchaV3Provider('6LdH3W0rA...'),
isTokenAutoRefreshEnabled: true
});This protects Firebase resources from abuse and is separate from the phone auth reCAPTCHA verifier.
Troubleshooting
This occurs after reCAPTCHA image selector challenge. Call resetRecaptchaVerifier() to fix.
The invisible reCAPTCHA token expires. Reset and retry the flow.
Phone must be in E.164 format: +[country code][number]. Example: +14155551234