Logo

dev-resources.site

for different kinds of informations.

How to Keep Your Custom Claims in Sync with Roles Stored in Firestore

Published at
4/25/2024
Categories
firebase
firestore
firebaseauth
Author
dennisalund
Categories
3 categories in total
firebase
open
firestore
open
firebaseauth
open
Author
11 person written this
dennisalund
open
How to Keep Your Custom Claims in Sync with Roles Stored in Firestore

A common question I often encounter, is how to maintain consistency between custom claims in Firebase Auth and role assignments stored in Firestore.

It is common in applications to have role-based authentication, where the access to resources is determined by a given role and where there are admin users have the authority to assign or revoke roles.

While Firestore provides an excellent backend to manage such information, it's crucial that this role data also be useful in authorization logic. In Firebase this is by best practice implemented in Firestore rules and Storage rules to declare resource access for database and files.

One of the ways to implement this is to only keep the data in Firestore, and another way to do it is to maintain the information in auth custom claims.

Both solutions has a few considerations to keep in mind.

Considering Your Options

As an illustration of the considerations, consider these security rules that are implementing each solution for two separate areas of the database and storage.

firestore.rules

service cloud.firestore {
  match /databases/{database}/documents {

    // Alt A: Using roles stored in Firestore user documents to determine access
    match /collection-a/{document} {
      allow read: if 'admin' in getUserRoles();
    }

    // Alt B: Using auth claims (role as an array) to determine access
    match /collection-b/{document} {
      allow read: if request.auth != null && 'admin' in request.auth.token.roles;
    }    

    // Function to get user roles from Firestore document
    function getUserRoles() {
      return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.roles;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

storage.rules

service firebase.storage {
  match /b/{bucket}/o {

    // Alt A: Using roles in user documents to determine access
    match /folder-a/{allPaths=**} {
      allow read: if 'admin' in getUserRoles();
    }

    // Alt B: Using auth claims to determine access
    match /folder-b/{allPaths=**} {
      allow read: if request.auth != null && 'admin' in request.auth.token.roles;
    }

    // Function to get user role from Firestore document
    function getRoleFromFirestore() {
      return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Option A: Firestore Document Lookups

If you choose to use Firestore document lookups for role-based access control, you're leveraging a straightforward method that works well with Firestore and Cloud Storage.

The primary drawback of this approach is its applicability is limited to just Firestore and Cloud Storage; it doesn't extend to Firebase's Realtime Database or other services that might benefit from integrated role-based access control.

It is also important to note that using Firestore documents to check authorization rules in both Firestore and Cloud Storage incurs additional document read costs each time an access check is performed.

Option B: Using Firebase Auth Custom Claims

The alternative involves replicating role information in Firebase Auth custom claims. This method offers broader integration across various services, including the Realtime Database and external API integrations where authentication data might be accessed via OAuth.

To implement this, a dedicated cloud function is essential for synchronizing role updates from Firestore documents to Firebase Auth custom claims. This function ensures that any changes in user roles within Firestore are promptly reflected in Firebase Auth.

Implementing the Cloud Function

The cloud function required for this task should:

  • Trigger on updates to the user document specifically related to role changes.
  • Update Firebase Auth custom claims to reflect these changes.
  • Maintain any other existing custom claims in the user's auth object.

Here’s a simple example of such a cloud function:

export const updateUserRoles = functions.firestore
  .document('/users/{userId}')
  .onUpdate(async (change, context) => {
    const beforeData = change.before.data();
    const afterData = change.after.data();

    // Check if roles have changed
    if (JSON.stringify(beforeData.roles) === JSON.stringify(afterData.roles)) {
      functions.logger.info('Roles are unchanged. Do nothing.');
      return null;
    }

    const uid = context.params.userId;
    const newRoles = afterData.roles;

    // Get the current auth user and merge the new roles into the claims
    const user = await admin.auth().getUser(uid);
    const newClaims = { ...user.customClaims, roles: newRoles };
    return admin.auth().setCustomUserClaims(uid, newClaims);
});
Enter fullscreen mode Exit fullscreen mode

Conclusion

Both approaches offer distinct advantages depending on your application's specific needs. Whether you prioritize broader service integration or a more focused, cost-effective solution within Firestore and Cloud Storage, understanding these options will empower you to make informed decisions about implementing role-based access control in your Firebase environment.

firestore Article's
30 articles in total
Favicon
Dev Video Review: Firestore Data Structure, Limitations, and IMHO
Favicon
Do you need a No Code tool for Firebase?
Favicon
Firebase: The Ultimate Backend for Your CMS
Favicon
NgSysV2-10.1: Firestore CRUD templates
Favicon
NgSysV2-3.3: A Serious Svelte InfoSys: Firebase D/b rules and Login
Favicon
NgSysV2-3.4: A Serious Svelte InfoSys: Rules-friendly version
Favicon
NgSysV2-3.5: A Serious Svelte InfoSys: Client-Server Version
Favicon
Dive into the world of serverless - GCP Edition
Favicon
Visualizing Firebase Data: Unlocking the Power of Real-Time Insights
Favicon
Implementing Batch Write Operations in Firestore with Express
Favicon
Enforcing Firebase App Check for Firestore with Initialization Configuration
Favicon
Retrieving User Roles from Firestore in a Next.js Application
Favicon
How to Keep Your Custom Claims in Sync with Roles Stored in Firestore
Favicon
Scheduling Events in Firebase Firestore with Server Timestamps
Favicon
Using Google Cloud Firestore with Django's ORM
Favicon
Firebase Realtime Database vs Cloud Firestore
Favicon
Real-Time Data Handling with Firestore: Tracking Pending Orders
Favicon
How to programmatically backup your Firestore database with simple steps
Favicon
Understanding Real-Time Data with Firebase Firestore in JavaScript
Favicon
Enabling Offline Capabilities in Firebase with IndexedDB Persistence
Favicon
Querying Firestore for Capital Cities with JavaScript
Favicon
Explorando o Firebase: Uma Plataforma Poderosa para Desenvolvimento de Aplicativos
Favicon
Using Firestore in Apps Script
Favicon
What is Flutter, how do I get started ??!!!
Favicon
How to Stream Data From Firebase to BigQuery Easily
Favicon
Retrieve a list of data under specific user collection in flitter
Favicon
Tentando não ficar pobre antes de ficar rico criando uma Startup de serviços de inteligência artificial
Favicon
Trying to Maintain a Workable Budget Creating a Chatbot Using GPT and Vector Database
Favicon
Uptime Monitoring with Firebase
Favicon
Firestore data modeling

Featured ones: