googleapis / nodejs-firestore

Node.js client for Google Cloud Firestore: a NoSQL document database built for automatic scaling, high performance, and ease of application development.
https://cloud.google.com/firestore/
Apache License 2.0
643 stars 149 forks source link

Run any DB request as a Firebase app's end-user #588

Open NawarA opened 5 years ago

NawarA commented 5 years ago

Thanks for stopping by to let us know something could be better!

PLEASE READ: If you have a support contract with Google, please create an issue in the support console instead of filing on GitHub. This will ensure a timely response.

Is your feature request related to a problem? Please describe. Hi, security rules are amazing. Its too bad they only work for client side code. On Node.js (server side code), I have to roll my own security rules.

Describe the solution you'd like I should be able to run database request as the applications end user.

On the client-side, Firebase makes use of the app's end-user's JWT. That information is used to match against security rules. On the server-side, you should allow a user to pass their end-user's JWT to Firestore. The JWT can then be processed, along with the resource, to check against security rules to ensure this app's end user has read / write access to the resource. It'd be simple to do this. We just need one more argument, the end user's JWT.

For example:

const Firestore = require('@google-cloud/firestore');

async function main( req, res ) {
    const firestore = new Firestore();
    const endUserJWT = req.jwt // given we have access to the end-user's JWT, which is by defintion a custom Firebase JWT , or the actual firebase JWT, then lets make a variable out of that

    const document = firestore.doc('posts/intro-to-firestore');
    console.log('Document created');

    // Enter new data into the document.
    await document.set({
      title: 'Welcome to Firestore',
      body: 'Hello World',
    }, endUserJWT );
    console.log('Entered new data into the document, accepted if UID is allowed to write, rejected otherwise');

    // Update an existing document.
    await document.update({
      body: 'My first Firestore app',
    }, endUserJWT );
    console.log('Updated an existing document, if this UID is allowed to update');

    // Read the document.
    let doc = await document.get( endUserJWT );
    console.log('Read the document, if the UID is allowed to read this resource');

    // Delete the document.
    await document.delete( endUserJWT );
    console.log('Deleted the document, if the UID is allowed to delete this resource');

};

main().catch(console.error);

This simple setup takes an optional second parameter, endUserJWT. The Firestore libabry can act as normal -- if no JWT is provided, universal access is granted, if a JWT is passed, run the request against security rules.

Suddenly, security rules work on the client-side (thanks to JWT access) and server-side (thanks to JWT access), lol.

Describe alternatives you've considered I have to roll my own security rules, which is a hassle, but I'm doing this now. Its a no brainer to add this feature to radically simplify Firestore dev's lives. Additional context Alt Text

laurentpayot commented 5 years ago

@NawarA you can have an end-user database on the server this way (I use it for my SSR PWA):

// server side: importing client-like NON-admin Firebase
import firebase from 'firebase/app'
import 'firebase/firestore'
import 'firebase/auth' // if you need auth
import {XMLHttpRequest} from 'xmlhttprequest'

global.firebase = firebase
global.XMLHttpRequest = XMLHttpRequest
ryanpbrewster commented 5 years ago

This is a really cool idea (and I'm glad you find security rules useful!).

We are working on first-class support for this functionality (I often call this behavior "user impersonation", because it allows admins to behave as though they were a specific user).

For now, if you want to use your security rules while acting as an admin, one pattern I've seen used is:

Here's a pretty thorough guide on how to use this pattern: https://medium.com/google-developers/controlling-data-access-using-firebase-auth-custom-claims-88b3c2c9352a

NawarA commented 5 years ago

Thanks. Yeah, allowing this server-side library to allow admin's to do "user impersonation" will get more people hooked into the NoOps features of Firestore. Thanks for the blog post by the way!

Since this doesn't exist, I've been developing and testing my own code. I'm basically writing server-side code that duplicates my Firestore Security Rules. Its redundant, but that's what developers are forced to do currently.

I'm looking forward to this feature getting released, thus creating parity between the client / server-side capabilities of leveraging security rules :)

NawarA commented 5 years ago

@laurentpayot I don't understand your code snippet. Did you finish writing the snippet?

Are you suggesting install an NPM package xmlhttprequest to fool Firestore into allowing it use xhr library, as if it was the front-end?

If so, then how does it resolve which user is "authed" into the system? I'm not 100% on this, but I'd imagine if you have 1 global Firestore app + 1 user Authed into it, then this would not work with concurrent users hitting your server? I'm developing an HTTP REST API. I expect N users to make N requests, at the same time, and be able to only access their resources, not each others resources.

Does your snippet cover this case? Help me understand. Thanks dude! :)

laurentpayot commented 5 years ago

@NawarA

Are you suggesting install an NPM package xmlhttprequest to fool Firestore into allowing it use xhr library, as if it was the front-end?

Yes exactly, I should have been more specific. Maybe I was a little off-topic 😉

As I said I use it for SSR so I want the server (in my case a Firebase function instance) to behave exactly like a client. In my special SSR case I do not need to access the database with privileges other than unauthenticated user's ones. The rules are run and I'm sure sensitive data are not accessed (my rules filter database access depending on users email domain, a bit like custom claims).

For your REST API I agree that for now custom claims are the way to go 👍

lookfirst commented 5 years ago

I would love to be able to use the Admin SDK as a specific user on a per request basis.

fgatti675 commented 4 years ago

Hi, we are developing a Firestore CMS and trying to develop a feature that fetches the root collections of a user's database and generates a schema based on the content. I've tried using the REST Firestore API but does not provide the feature. On the contrary, this library provides the feature to list root collections but I can't use OAuth on behalf of the user for this purpose. Any plans on implementing user impersonation? :)

NawarA commented 4 years ago

This is actually a really easy feature for the Firestore team to add.

As I mentioned earlier on this thread, to make this work, on the backend just allow dev's to pass in the user's JWT to the DB action.

let doc = await document.get( endUserJWT );

If endUserJWT is undefined, Firestore grants universal access (what we have today). If it is defined and the JWT is a valid client-side generated JWT that represents a user, then the Firestore checks its security rules to ensure the users is allowed to the do the action.

This feature is easy to add and maintain for the FS team (ie. its a very isolated service) and allows the firestore community to use security rules on the backend (which is important).

lrodorigo commented 3 years ago

Any update about this?

This can prevent users from duplicating security rules into Firebase rules and backend-code, which is really a mess. For me it questions if it'is really a "good-practice" to use Firestore directly from the frontend instead of writing a well-structured backend service, which avoids the duplication of the access rules.