Anonymous-First Auth for Chrome Extensions: Let Users Start Before They Sign Up
The anonymous-first Firebase Auth pattern for chrome extensions: guest uids at install, uid-preserving account linking with linkWithCredential, conflict handling, and when not to use it.
TL;DR: Give every install a Firebase anonymous uid at startup. Gates, usage counters, and even purchases attribute to that uid from minute one, no forced sign-up. When the user eventually signs in,
linkWithCredentialupgrades the same account in place, so the uid (and everything keyed on it) survives. If the credential already belongs to an existing account, sign into that account instead of merging; never merge silently. Enforce anything with stakes (trials, credits) server-side, because a reinstall mints a fresh uid.
Most extensions ask for sign-in at exactly the wrong moment: before the user has seen any value. Checkout usability research (the Baymard Institute’s long-running studies are the standard reference) consistently finds forced account creation among the top reasons users abandon a flow. An extension that demands a Google login on first open pays that tax on every install, and gets review friction from the Chrome Web Store on top (reviewers are fresh installs too).
Anonymous-first flips the order: identity friction late, attribution early.
The pattern
Firebase anonymous auth creates a real user (real uid, real ID token) without any interaction:
import { signInAnonymously } from "firebase/auth/web-extension";
// background, at startup: every install gets a guest uid
const { user } = await signInAnonymously(auth);
From that moment your backend has a stable key. Usage counters live at
usage/{uid}, entitlements at customers/{uid}, gate events are
auth-verified, all before the user has typed anything. In ExtensionStart this
is one env flag (WXT_ANONYMOUS_AUTH=true); the background signs in the guest
at startup and the rest of the system doesn’t care whether a uid is anonymous.
The payoff shows up in three places:
- Gates and metering work pre-signup. “Paywall after 10 actions” needs a counter; the counter needs a key. Guest uids make day-zero monetization mechanics possible without an account wall.
- Purchases work pre-signup. A checkout session can carry the guest uid; the payment webhook writes entitlements under it. Users can literally pay before registering.
- Nothing is lost at sign-up. Because of linking (next section), the upgrade preserves everything.
Linking: the upgrade that keeps the uid
The naive implementation signs the anonymous user out and a real user in: new
uid, orphaned data, angry paying guest. The correct primitive is
linkWithCredential:
it attaches the interactive credential (Google, email/password) to the
current user, so the anonymous account becomes the permanent account.
ExtensionStart’s strategy does this automatically inside signIn():
// packages/core-auth/src/strategy.ts (trimmed)
const current = auth.currentUser;
if (current?.isAnonymous) {
try {
const result = await linkWithCredential(current, credential);
return toAuthUser(result.user); // same uid: upgraded in place
} catch (error) {
if (!isLinkConflict(error)) throw error;
// The credential already has an account — sign into it instead.
}
}
const result = await signInWithCredential(auth, credential);
Same uid before and after means customers/{uid} and usage/{uid} need no
migration step at all. That’s the whole trick.
Conflicts: never merge silently
The interesting case: the user taps “Sign in with Google”, but that Google account already has a Firebase account (they used your extension on another machine last year). Linking then fails with one of a small set of codes:
// packages/core-auth/src/strategy.ts
export function isLinkConflict(error: unknown): boolean {
const code = /* error.code */;
return (
code === "auth/credential-already-in-use" ||
code === "auth/email-already-in-use" ||
code === "auth/provider-already-linked" ||
code === "auth/account-exists-with-different-credential"
);
}
The right recovery is to sign into the existing account: the user asked to be that person, so be that person. The guest session’s server-side data stays behind under the old uid. That’s a deliberate loss: automatically merging two accounts’ counters, purchases, and documents is a data-integrity and consent problem (which account’s subscription wins? whose settings?). If a merge ever matters for your product, make it an explicit, user-confirmed server-side operation, never a silent client default.
Two UX notes that follow from the same logic:
- Email sign-up with an in-use address should surface “email in use” and steer to sign-in, not attempt anything clever.
- Sign-out returns to a fresh guest session, not the old guest uid. Reusing it would resurrect a session the user chose to leave.
And one presentation rule: anonymous users should count as signed out in your UI and gating identity. They see the sign-in surface; converting them is the sign-in wall’s job. Anonymous-first is plumbing, not a substitute for having accounts.
The abuse boundary: uids are cheap
A reinstall creates a brand-new guest uid. So anything with stakes must not trust “one uid = one human”:
- Free trials: enforce trial-once server-side, on the entitlement record
(a
trialUsedflag the payment webhook sets); note that Stripe also keys trial eligibility to the customer/payment method. A reinstall resets the local counter theater; it does not reset the server’s memory. - Credits / quotas: increment server-side from auth-verified events; the client’s counter is timing UX for showing walls, never authority.
This split (client counters decide when to show a wall, the server decides what is true) is the same server-authority principle covered in the Stripe payments guide.
When NOT to use anonymous-first
Honest list, because the pattern isn’t free:
- Identity-centric products. If the extension is useless without sync, teams, or an existing SaaS account, guest uids just delay the inevitable screen and add account records.
- Nothing to attribute. No gates, no metering, no pre-signup purchases? Then anonymous accounts are dead weight; plain “signed out until sign-in” is simpler.
- Account hygiene has a cost. Every install creates a user. Firebase’s automatic clean-up can delete anonymous accounts after 30 days, but a deleted guest’s server-side rows become orphans, so decide your retention story up front.
- Strict-identity contexts. Compliance regimes that require a verified identity before any data processing leave no room for guests.
What ExtensionStart ships
Anonymous-first behind one flag, uid-preserving linking with conflict fallback built into the auth strategy, fresh-guest sign-out, gates and billing that treat guest uids as first-class, and server-side trial/credit enforcement so cheap uids can’t buy anything twice. If you’re wiring this by hand, the sequence above is the map; if you’d rather start past it, that’s the kit.
Frequently asked questions
Does linkWithCredential change the Firebase uid?
No; that's the entire point. Linking a Google or email credential to an anonymous user upgrades the same account in place: the uid is preserved, so any server-side data keyed on it (entitlements, usage counters, purchases) survives the sign-in untouched.
What happens if the Google account is already registered when an anonymous user signs in?
Linking fails with a conflict error such as auth/credential-already-in-use. The correct recovery is to sign the user into the existing account instead. The anonymous session's server-side data stays behind under the old uid; merging two accounts automatically would silently combine data from different people, so it should never happen without explicit consent.
Can users abuse anonymous accounts to reset a free trial?
They can get a fresh uid by reinstalling, so anything with stakes must be enforced server-side. Tie trial usage to the entitlement record and to the payment provider's customer/card, not just the uid, and treat client-side counters as UX only.
Do anonymous Firebase accounts cost money or pile up forever?
They're free to create, but every install adds a user record. Firebase offers automatic clean-up that deletes anonymous accounts older than 30 days, and anonymous accounts don't count toward billing on the standard auth tiers. Enable clean-up only if you accept that a dormant guest's server-side data becomes orphaned.
When should I not use anonymous-first auth?
Skip it when your product is meaningless without an identity (cross-device sync, team features), when a hard compliance requirement demands a verified identity up front, or when nothing in your extension attributes data to a user before sign-up; in that case the extra accounts buy you nothing.
Can an anonymous user actually purchase a subscription?
Yes. Checkout attributes the purchase to the guest uid, the webhook writes entitlements under that uid, and when the user later signs in the uid is preserved by linking, so the purchase follows them. Prompting for sign-in around checkout is still wise so they can recover access on a new machine.