Skip to content

Conversation

@gyula-s
Copy link

@gyula-s gyula-s commented Nov 27, 2025

Cache the fetched password policy per Firebase app to avoid
redundant API calls to the identity toolkit endpoint.

Description

Every time you call validatePassword(), the library was making an API call to Firebase's Identity Toolkit
to fetch the password policy. When a user types their password character by character (e.g.,
"P-a-s-s-w-o-r-d-1-2-3-$"), that's 12 API calls just for one password entry. If users backspace and
retype, it adds up fast.

Firebase has rate limits on this endpoint, so you hit:
QUOTA_EXCEEDED: Exceeded quota for getting password policy

The Fix (Aligned with Firebase JS SDK)

This implementation mirrors the Firebase JS SDK's approach for password policy caching:

Instance-level caching

  • Password policy is now cached on the Auth instance after first fetch
  • Separate caches for project-level (_projectPasswordPolicy) and tenant-specific policies (_tenantPasswordPolicies)

Lazy loading

  • Policy is fetched only on the first validatePassword() call
  • Subsequent calls use the cached policy (zero API calls)

Multi-tenant support

  • Each tenant gets its own cached policy
  • Project-level policy used when tenantId is null

Reactive cache invalidation

  • Cache is refreshed when PASSWORD_DOES_NOT_MEET_REQUIREMENTS error occurs
  • This handles the edge case where policy changes mid-session

Schema version validation

  • Rejects unsupported schema versions (matching JS SDK behavior)

Related issues

#8780

Release Summary

Fixed validatePassword() causing QUOTA_EXCEEDED errors by caching the password policy
per Auth instance. Implementation matches Firebase JS SDK behavior including tenant support
and reactive cache invalidation.

Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
    • Yes
  • My change supports the following platforms;
    • Android
    • iOS
    • Other (macOS, web)
  • My change includes tests;
    • e2e tests added or updated in packages/\*\*/e2e (no e2e behaviour changed)
    • jest tests added or updated in packages/\*\*/__tests__
  • I have updated TypeScript types that are affected by my change.
  • This is a breaking change;
    • Yes
    • No

Test Plan

Jest tests added covering:

  • Caching behavior (lazy loading, subsequent calls use cache)
  • Tenant isolation (separate caches per tenant)
  • Project vs tenant cache separation
  • Schema version validation
  • Cache invalidation via _recachePasswordPolicy()
  • Input validation (null/undefined password)

  Cache the fetched password policy per Firebase app to avoid
  redundant API calls to the identity toolkit endpoint.
@vercel
Copy link

vercel bot commented Nov 27, 2025

@gyula-s is attempting to deploy a commit to the Invertase Team on Vercel.

A member of the Team first needs to authorize it.

@CLAassistant
Copy link

CLAassistant commented Nov 27, 2025

CLA assistant check
All committers have signed the CLA.

@mikehardy
Copy link
Collaborator

🤔 Hmmm - I really appreciate the effort here, the tests are great as well. I'm just not sure we can make caching decisions like this for library consumers. I believe this would fit well at the app level as a React useState var set via useEffect in the component where passwords were set. I am just not sure there is any caching policy we can pick that will be correct in 100% of the consuming app use cases

@mikehardy mikehardy added the Workflow: Waiting for User Response Blocked waiting for user response. label Nov 29, 2025
@gyula-s
Copy link
Author

gyula-s commented Dec 1, 2025

🤔 Hmmm - I really appreciate the effort here, the tests are great as well. I'm just not sure we can make caching decisions like this for library consumers. I believe this would fit well at the app level as a React useState var set via useEffect in the component where passwords were set. I am just not sure there is any caching policy we can pick that will be correct in 100% of the consuming app use cases

I respectfully disagree - caching is important here, otherwise it will (and currently does) spam the server on every keystroke during password validation on mobile, which breaks the user experience.

The Firebase JS SDK actually implements this same caching pattern, but more robustly:

  • Cache stored at the Auth instance level (per-tenant support)
  • Lazy-loaded on first validatePassword() call
  • Validation runs 100% client-side using the cached policy
  • Cache is only refreshed reactively when PASSWORD_DOES_NOT_MEET_REQUIREMENTS errors occur from actual auth operations

This is a "fetch once, validate locally, refresh on failure" pattern - no TTL, no complexity, just pragmatic caching that ensures the policy is always fresh when it matters.

I'm currently working on an implementation that mimics the JS SDK much more closely, including tenant support and reactive cache invalidation.

  Refactor validatePassword to use instance-level caching with tenant
  support and reactive cache invalidation, matching the Firebase JS SDK
  implementation.

  Changes:
  - Move cache from module-level to Auth instance (_projectPasswordPolicy,
    _tenantPasswordPolicies) for proper multi-app and multi-tenant support
  - Add reactive cache invalidation on PASSWORD_DOES_NOT_MEET_REQUIREMENTS
    errors in createUserWithEmailAndPassword, signInWithEmailAndPassword,
    and confirmPasswordReset
  - Add schema version validation (rejects unsupported versions)
  - Delegate modular API validatePassword to auth instance

  This ensures the password policy is:
  - Fetched once per tenant/project (lazy loading)
  - Validated locally without server calls
  - Automatically refreshed when the server rejects a password

  Matches: https://github.com/firebase/firebase-js-sdk/blob/main/packages/auth/src/core/auth/auth_impl.ts
@gyula-s
Copy link
Author

gyula-s commented Dec 1, 2025

PR is now updated @mikehardy

Copy link
Collaborator

@mikehardy mikehardy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I respectfully disagree - caching is important here, otherwise it will (and currently does) spam the server on every keystroke during password validation on mobile, which breaks the user experience.

I do agree with the need, my question was just where to cache, and how to do so without making a hidden policy decision on behalf of API consumers

Cache is only refreshed reactively when PASSWORD_DOES_NOT_MEET_REQUIREMENTS errors occur from actual auth operations

Now this is a pattern that can work and doesn't involve deciding for the API consumers, plus has the benefit of knowing firebase-js-sdk "blesses" it upstream as a pattern.

Honestly, I had thought something like this would work but I'll admit that I hesitated and in the end did not ask as it was a pretty big departure from the original implementation and a lot more work for a community-provided PR 😅 . Normally community PR authors are a bit more casual and not quite as dedicated! I completely underestimated your interest in the feature though :-), my apologies. I appreciate your implementing something that does the caching without making policy decisions for app consumers, the implementation looks fantastic, the testing looks great as well.

I left what I would consider to be the tiniest of nitpicks on the tests mostly because if the rest of the code is going to be basically perfect might as well make the tests perfect as well. But maybe my analysis on the beforeEach/afterEach chunks is incorrect?

What do you think?

Otherwise - and speaking specifically of the feature implementation - definitely ready to merge. Thank you

@mikehardy
Copy link
Collaborator

does appear there is a legitimate issue that the tests have probed and either the test isn't quite correct or there may be a small implementation detail? If you check the CI run logs you'll see it...

Copy link
Author

@gyula-s gyula-s left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I've replied to your comments.
The tests were failing yesterday (and coverage).
I think I've addressed some of it, but locally it's a bit of a pain to get the same results.
waiting for GHA to report back on any failures to carry on.

@gyula-s gyula-s requested a review from mikehardy December 2, 2025 10:45
Copy link
Collaborator

@mikehardy mikehardy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 pending CI, thanks @gyula-s

@mikehardy mikehardy added Workflow: Pending Merge Waiting on CI or similar and removed Workflow: Waiting for User Response Blocked waiting for user response. labels Dec 2, 2025
@gyula-s gyula-s requested a review from mikehardy December 2, 2025 15:13
@gyula-s
Copy link
Author

gyula-s commented Dec 2, 2025

sorry @mikehardy
these tests are a pain and I can't get them run locally, same with codecov.
have a look at the changes and approve if you're happy...
let's see what fails this time

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants