Home
> How to Update a User Profile in Firebase
Get updates on future FREE course and blog posts!
Subscribe

How to Update a User Profile in Firebase

10 min read

Jonathan Gamble

jdgamble555 on Sunday, May 12, 2024 (last modified on Tuesday, May 28, 2024)

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.

  1. While there is a user().onCreate trigger, there is NOT a user().onUpdate trigger.
  2. There is no way to prevent users from updating their own record in Firebase Authentication once logged in.
  3. 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.

  1. Use the Firebase Authentication database if you have no need for complex profile features and you want to save on reads.
  2. Use Firestore or any external database if you have any complex data and donā€™t care about keeping Firebase Authentication database in sync.
  3. Use both databases if keeping the data in sync is not mandatory.

Update Profile Demo App

Repo: GitHub

Demo: Vercel Serverless

J


Related Posts

Ā© 2024 Code.Build