Firebase Authentication is its own Database, separate from Firestore. You can use it securely with any database you like to handle your login needs. However, this database has no filterable queries and is only available to the client for the logged-in user. How you store your user's profile information depends on your app's complexity.
TL;DR#
A userās profile in Firebase specifically refers to the photoURL
and the displayName
. These two items can be stored and updated in Firebase without using Firestore or Firebase Realtime Database. You can update them by calling the updateProfile
function in Firebase Auth SDK. You must choose how to sync your data with other databases.
Where to Store the User?#
If you want the best of both worlds, you can store your user profile information inside Firestore and Firebase Authentication. Copying the data on sign-up is easy. However, updating a user can be trickier.
- While there is a
user().onCreate
trigger, there is NOT auser().onUpdate
trigger. - There is no way to prevent users from updating their own record in Firebase Authentication once logged in.
- Firebase Authentication is free, but Firestore charges per read.
Handling Synchronization#
There are trade-offs in everything we do.
1. Use Firebase Auth as the Main Database#
This is the easiest and best way for small apps. There is no reason to have a separate users
collection when no joins are involved, or you don't need to have a complicated user profile. This can be the best route when dealing only with the user profile information.
Here, we have an updateUser
function that can update the displayName
and the photoURL
.
export const updateUser = async (
displayName: string,
photoURL: string
) => {
if (!auth.currentUser) {
return {
error: 'Not Logged In!'
};
}
try {
await updateProfile(auth.currentUser, {
displayName,
photoURL
});
} catch (e) {
if (e instanceof FirebaseError) {
return {
error: e.message
};
}
}
return {};
};
š Get the auth
object using your Framework Setup.
This puts the boilerplate in one place. You can call it in your component while only returning an error
if there is one.
const { error } = await updateUser(displayName, photoURL);
if (error) {
// handle error
...
return;
}
// handle success
...
If you need to get the user information for a public profile on a different user than is logged in, you would have to use Firebase Admin on the server.
// Get user from Firebase Authentication database
export const getProfile = async (uid: string) {
const { displayName, photoURL } = await adminAuth.getUser(uid);
// Only return the profile values for security
return {
displayName,
photoURL
};
};
š Get the adminAuth
object using your Firebase Admin Setup.
2 - Update Both Databases on the Client#
This version is more obvious but wonāt be as safe.
export const updateUser = async (
displayName: string,
photoURL: string
) => {
// Update Firebase Authentication Database
...
// Update Firestore Database
try {
await updateDoc(
doc(db, 'users', auth.currentUser!.uid),
{
displayName,
photoURL
}
)
} catch(e) {
if (e instanceof FirebaseError) {
return {
error: e.message
}
}
}
return {};
};
3 - Update Both Databases with a Firestore Trigger#
If you want to align both databases, you can trigger Firestore to automatically update the Firebase Authentication database when a change occurs.
import { firestore, https } from 'firebase-functions';
import { auth } from 'firebase-admin';
const adminAuth = auth();
export const updateAuthProfile = firestore
.document('users/{userId}')
.onUpdate(async (change, context) => {
const beforeData = change.before.data();
const afterData = change.after.data();
const userId = context.params.userId;
// Initialize an object to hold updates
const updates: Partial<{
displayName: string,
photoURL: string
}> = {};
// Check if displayName has been modified
if (beforeData.displayName !== afterData.displayName) {
updates.displayName = afterData.displayName;
}
// Check if photoURL has been modified
if (beforeData.photoURL !== afterData.photoURL) {
updates.photoURL = afterData.photoURL;
}
if (!Object.keys(updates).length) {
console.log('No relevant changes detected in user profile');
return;
}
// Attempt to update the user's Authentication profile
try {
await adminAuth.updateUser(userId, updates);
console.log(`Updated user profile for user ${userId}:`, updates);
} catch (error) {
console.error('Error updating user profile:', error);
throw new https.HttpsError(
'internal',
'Failed to update user profile in Auth',
error
);
}
});
Generation 2#
In case youāre using Generation 2.
import { onDocumentUpdated } from 'firebase-functions/v2/firestore';
import { auth } from 'firebase-admin';
import { https } from 'firebase-functions/v2';
const adminAuth = auth();
export const updateAuthProfile = onDocumentUpdated('users/{userId}',
async (event) => {
const beforeData = event.data!.before.data();
const afterData = event.data!.after.data();
const userId = event.params.userId;
// Initialize an object to hold updates
const updates: Partial<{
displayName: string,
photoURL: string
}> = {};
// Check if displayName has been modified
if (beforeData.displayName !== afterData.displayName) {
updates.displayName = afterData.displayName;
}
// Check if photoURL has been modified
if (beforeData.photoURL !== afterData.photoURL) {
updates.photoURL = afterData.photoURL;
}
if (!Object.keys(updates).length) {
console.log('No relevant changes detected in user profile');
return;
}
// Attempt to update the user's Authentication profile
try {
await adminAuth.updateUser(userId, updates);
console.log(`Updated user profile for user ${userId}:`, updates);
} catch (error) {
console.error('Error updating user profile:', error);
throw new https.HttpsError(
'internal',
'Failed to update user profile in Auth',
error
);
}
});
š You could equally use an SQL trigger function or a trigger function in your database of choice.
Caveats#
Because there is no guarantee that both databases will stay in sync if you have any client Firebase features, you must choose your principal database; your client API key will be easily available to any programmer.
- Use the Firebase Authentication database if you have no need for complex profile features and you want to save on reads.
- Use Firestore or any external database if you have any complex data and donāt care about keeping Firebase Authentication database in sync.
- Use both databases if keeping the data in sync is not mandatory.
Repo: GitHub
Demo: Vercel Serverless
J