Field level access control - Firestore to MongoDB

We are migrating from Firestore to MongoDB.

In Firestore they have security rules which specify which role can access which set of collections / sub-collections, documents, or even specific fields in a document

Example :

match / databases / { database } / documents {
    match / student / { documentId }{
      allow read;
      allow write: if ((isSuperAdmin() || isManager() || (isTeacher() && request.resource.data.diff(resource.data).affectedKeys().hasOnly(['grade'])) && noStudentWithSameDocId())
      }
    function noStudentWithSameDocId() {
        return get(/databases/$(database) / documents / student / $(documentId)) == null;
    }
}

so for example we have a student collection

const student = { 
  name: string
  class: string
  grade: number
  fees: number
} 

and 3 roles :

super_admin
manager
teacher

super_admin role can edit all the fields in the student document and can delete a student document

the manager role can edit all the fields in the student document but cannot delete a student document

the teacher role can only edit the grade field in the student document (cannot delete the document) and cannot read the fees field from the student

  • what is the best way of achieving this?
  • My take so far is to have it in consecutive middleware
  • but is there any library or MongoDB built-in tool that eases this?

Thank you in advance

1 Like

Hi :wave: @Hassan_Edelbi,

Welcome to the MongoDB Community forums :sparkles:

MongoDB has a built-in feature called “Field-Level Redaction” that can be used to restrict access to certain fields within a document. It uses the $redact pipeline operator to restrict the contents of the documents based on information stored in the documents themselves. This feature will be particularly useful in your case if you need to hide the “fees” field from users who only have a “teacher” role.

If you are creating a user at the database level, you can utilize the built-in roles. However, for application-level users, I’ll recommend using middleware in your application code. In your application code, you can define the roles and their corresponding permissions, and then use middleware to verify if the user making the request has the required permissions before allowing the request to proceed.

Sharing the code snippet for your reference using Express.js middleware:

const express = require('express');
const app = express();

// Define middleware to check if the user has the "super_admin" role
const checkSuperAdmin = (req, res, next) => {
  if (!req.user.roles.includes('super_admin')) {
    return res.status(403).send('Forbidden');
  }
  next();
};

// Define middleware to check if the user has the "manager" role
const checkManager = (req, res, next) => {
  if (!req.user.roles.includes('manager')) {
    return res.status(403).send('Forbidden');
  }
  next();
};

// Define middleware to check if the user has the "teacher" role
const checkTeacher = (req, res, next) => {
  if (!req.user.roles.includes('teacher')) {
    return res.status(403).send('Forbidden');
  }
  next();
};

// Route for the super_admin to edit all fields and delete the document
app.put('/student/:id', checkSuperAdmin, (req, res) => {
});

app.delete('/student/:id', checkSuperAdmin, (req, res) => {
});

// Route for the manager to edit all fields but not delete the document
app.put('/student/:id', checkManager, (req, res) => {
});

// Route for the teacher to edit the grade field only
app.put('/student/:id', checkTeacher, (req, res) => {
  // Update the "grade" field in the student document with the new value
  // And Return the updated student document (without the "fees" field)
});

Please note that this is for the above sample document created, however, this might change as per your document. Also, I would suggest having thorough testing before using it in production.

I hope it helps!

Best,
Kushagra

2 Likes

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.