One Account, Two Doors - Building Authentication Across Platforms
Imagine you are building a house with two front doors. One door is for guests arriving by car (the mobile app), and another for those walking in from the street (the website). Both doors need to lead to the same living room, and you need to recognize who is who.
This is the challenge I faced with Booklynx. Users can create accounts through the Android app or through this website. Both need to work seamlessly, creating the same user experience regardless of where someone signs up.
The Problem: Two Entry Points, One User
When a user creates an account, several things need to happen:
- A Firebase Authentication account is created (email/password or Google Sign-In)
- A user document is created in the database with profile information
- A unique friend code is generated (like ABC23XYZ)
- Default libraries are set up (Reading, Want to Read, Finished, etc.)
- Privacy settings and preferences are initialized
If I put this logic in the mobile app, what happens when someone signs up on the website? I would have to duplicate all that code. And if I ever change how user setup works, I would have to update it in two places. That is a recipe for bugs.
The Solution: Cloud Functions
Instead of putting the user creation logic in each client (app and website), I moved it to a Cloud Function. Think of it as a trusted butler who lives in the cloud. Both doors can ring the butler, and the butler handles everything consistently.
Mobile App
- • Signs up user
- • Calls Cloud Function
Website
- • Signs up user
- • Calls Cloud Function
Cloud Function
- • Creates user document
- • Generates friend code
- • Sets up libraries
Now, whether you sign up on your phone or on this website, the same Cloud Function runs. It creates your profile, generates your unique friend code, and sets up your default libraries. One piece of code, used everywhere.
// Both app and website call this function after signup
export const createUserSetup = onCall(async (request) => {
const uid = request.auth.uid;
// Generate unique 8-character friend code
const userCode = await generateUniqueCode();
// Create user document with defaults
await db.collection('users').doc(uid).set({
email: request.auth.token.email,
displayName: request.data.displayName || 'Reader',
userCode: userCode,
createdAt: serverTimestamp(),
// ... privacy settings, notification preferences, etc.
});
// Create default libraries
await createDefaultLibraries(uid);
return { success: true, userCode };
});Why Email Verification Matters
There is another challenge with authentication: spam and bots. Without verification, anyone (or any script) could create thousands of fake accounts, pollute the voting system, or flood the community with spam.
That is why Booklynx requires email verification before you can vote on features or participate in discussions. When you sign up, we send a verification link to your email. Until you click it, you can browse the site but cannot interact with the community.
Why this protects everyone
The Exception: Social Sign-In
Here is the clever part: if you sign in with Google or Apple, you skip the email verification step entirely. Why? Because Google and Apple have already verified your email for you.
When you use "Sign in with Google," Google confirms that you own that email address. They have already done the hard work of fighting bots and fake accounts. I can trust their verification, so you get instant access to all features.
The Technical Dance
Getting all this to work smoothly required careful choreography:
- Firebase Authentication handles the actual login (passwords, Google tokens, etc.)
- Cloud Functions create the user profile and setup after authentication succeeds
- Real-time listeners on the website detect when the Cloud Function finishes, so your profile appears instantly
- Security rules ensure only the Cloud Function can create certain data, preventing tampering
The trickiest part was handling the brief moment between when you sign up and when the Cloud Function finishes creating your profile. The website now uses a real-time listener that automatically updates the moment your data is ready. No refresh needed.
The Result
Lessons Learned
1. Centralize Critical Logic
Anything that must work the same way everywhere belongs in a Cloud Function or backend service. Client code (apps, websites) should be thin and focused on the user interface.
2. Trust, But Verify
Email verification is a small friction that provides huge protection. But when a trusted provider like Google has already verified someone, do not make them jump through hoops again.
3. Plan for Multiple Clients
Even if you are building just an app today, design your backend as if you will have a website tomorrow. It is much easier to add a second door when the butler already knows what to do.