Sign-in
The kit ships two Google sign-in paths, plus email/password and anonymous sign-in, all built for extensions. Every flow runs in the background service worker; your UI never touches Firebase, it reads who is signed in from storage.
Choose your Google sign-in path
Section titled “Choose your Google sign-in path”Both paths are fully wired. One env var picks between them:
| Web auth flow | Offscreen popup | |
|---|---|---|
| Browsers | Chrome, Edge, Firefox | Chrome and Edge only |
| Setup | Create a Google OAuth client (about 5 console minutes) | Deploy the bundled sign-in page to Firebase Hosting |
| How to pick it | Set WXT_GOOGLE_OAUTH_CLIENT_ID | Leave WXT_GOOGLE_OAUTH_CLIENT_ID empty |
| Sign-in UX | Browser account chooser | A small popup window |
| Watch out for | The redirect URI embeds your extension ID, which changes if you load unpacked from a new path | One extra Hosting deploy; no Firefox |
If unsure, use the web auth flow: it covers Firefox and is the production default. Independent of the choice:
- Email/password always works alongside, no OAuth client needed
(
emailSignIn/emailSignUp/emailPasswordResetmessages). - Anonymous-first (
WXT_ANONYMOUS_AUTH=true): every install starts as a guest uid, and sign-in upgrades that uid in place, so purchases and counters survive. Turn it on when gates, counters, or purchases should work before sign-up.
Set up your Firebase project
Section titled “Set up your Firebase project”Install the Firebase CLI and sign in first: npm i -g firebase-tools,
then firebase login. Deploys also require the Blaze plan; the
free-tier quota covers development.
The automated path (recommended)
Section titled “The automated path (recommended)”pnpm create extstart --firebaseThe wizard creates or picks a project, creates a web app, and writes its
SDK config everywhere it lives: apps/extension/utils/firebase.ts,
backend/firebase-hosting/public/signInWithPopup.js, both .firebaserc
files, and the URLs in apps/extension/.env. It then prints a
deep-linked checklist of the console steps no CLI can do (sign-in
providers, Blaze plan, OAuth client). Paste the OAuth client ID when
offered and it writes WXT_GOOGLE_OAUTH_CLIENT_ID too. Safe to re-run;
headless flags are in the CLI reference.
The manual path
Section titled “The manual path”- Firebase console: create a project.
- Add a Web App and copy its config into
apps/extension/utils/firebase.ts(theTODOmarker). Until then the extension shows “Connect your Firebase project” in every surface. - Authentication → Sign-in method: enable Google and Email/Password (and Anonymous for anonymous-first). This step is manual even on the automated path.
- Set
VITE_FIREBASE_HOSTING_URL=https://<project>.firebaseapp.cominapps/extension/.env.
The Google OAuth client (web-auth-flow path)
Section titled “The Google OAuth client (web-auth-flow path)”- Load the extension once and copy its ID from
chrome://extensions. - Google Cloud console (same project) → Credentials → Create OAuth
client → Web application → authorized redirect URI:
https://<extension-id>.chromiumapp.org/. - Set
WXT_GOOGLE_OAUTH_CLIENT_ID=<client-id>.apps.googleusercontent.comin.env.
The offscreen popup path
Section titled “The offscreen popup path”If WXT_GOOGLE_OAUTH_CLIENT_ID is empty, the background completes
sign-in in an offscreen document that loads a page from your Firebase
Hosting. That page needs your config too:
-
Paste your Firebase web config into
backend/firebase-hosting/public/signInWithPopup.js(theTODOmarker).pnpm create extstart --firebasewrites it for you. -
Deploy it, from
backend/firebase-hosting/:Terminal window firebase use <your-project-id>firebase deploy --only hosting -
Set
VITE_FIREBASE_HOSTING_URL=https://<your-project-id>.firebaseapp.cominapps/extension/.env. Firebase Hosting serves the reserved/__/auth/*helpers on that origin, so the page can’t run locally.
If you skip the deploy, clicking “Sign in with Google” opens a popup that closes again silently: the page still carries placeholder config, so the auth result never reaches your extension.
Anonymous-first: how linking behaves
Section titled “Anonymous-first: how linking behaves”With WXT_ANONYMOUS_AUTH=true, every install gets a guest uid at startup:
- Gates, counters, and purchases attribute to that uid from minute one.
- Interactive sign-in upgrades in place:
linkWithCredentialkeeps the uid, so entitlements and counters survive untouched. - Conflicts: if the credential already belongs to an account, linking fails and the strategy signs into the existing account instead. Accounts are never merged silently; the guest’s server-side data stays under the old uid.
- Signing out returns to a fresh guest session.
- Anonymous users count as signed out for gate identity; converting them is the sign-in wall’s job.
The token rules
Section titled “The token rules”Enforced by lint and architecture, not convention:
- ID tokens never leave the background. UI and content scripts send
messages (
billingCheckout,gateFeature, …); the background attaches the token to the API call. - Content scripts get proxied state only:
storage.localsnapshots and the message bus. They never import Firebase. - Single writer: the background’s
onAuthStateChangedis the only writer ofstorage.local.user. UI reads storage, so every surface shows the same state and survives service-worker restarts. - Logout everywhere: sign-out calls
POST /auth/revoke(revokeRefreshTokens) before clearing local state. Best-effort: local sign-out proceeds even if the network call fails. - Ephemeral or token-ish data belongs in
storage.session, neverstorage.sync.
The kit imports firebase/auth/web-extension, not firebase/auth; the
standard build assumes DOM APIs a service worker doesn’t have. Every
gated read awaits authStateReady(). The strategy handles both for you.