2

My Firebase web app requires administrator access, i.e., the UI should show a few things only for admins (an 'administrator' section). I came up with the below as a means to authorize the UI to display the admin section for valid admins only. My question is, good or bad? Is this a sound means of authorizing? ...so many ways to do this. This particular way requires me to configure admins in the security rules (vs in a node/tree in a db/firestore)

My idea is that if the .get() fails due to unauthorized access, I tell my app logic the user is not an admin, if the .get() succeeds my logic shows the 'admin' sections. Of course, the 'sections' are just HTML skeletons/empty elements populated by the database so even if the end user hacks the JS/logic, no real data will be there - only the empty 'admin section' framework.

function isAdmin(){
    return new Promise(function(resolve, reject){
        var docRef = firebase.firestore().collection("authorize").doc("admin");
        docRef.get().then(function(result) {
            if (result) {
                resolve (true);
            }
        }).catch(function(error) {
            resolve (false);
        });
    });
}

The firestore rule specifies the 'admins' by UID.

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.auth.uid == "9mB3UxxxxxxxxxxxxxxxxxxCk1";
    }
  }
}
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Ronnie Royston
  • 16,778
  • 6
  • 77
  • 91

2 Answers2

2

You're storing the role of each user in the database, and then looking it up in the client to update its UI. This used to be the idiomatic way for a long time on realtime database, and it still works on Firestore.

The only thing I'd change is to have the rules also read from /authorize/admin, instead of hard-coding the UID in them. That way you only have the UID in one place, instead of having it in both the rules and the document.

But you may also want to consider an alternative: set a custom claim on your admin user, that you can then read in both the server-side security rules (to enforce authorized access) and the front-end (to optimize the UI).

To set a custom claim you use the Firebase Admin SDK. You can do this on a custom server, in Cloud Functions, but in your scenario it may be simpler to just run it from your development machine.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • dangit, [Control Access with Custom Claims and Security Rules](https://firebase.google.com/docs/auth/admin/custom-claims#access_custom_claims_on_the_client) is exactly the proper tool for the job and there I was using a hammer again. Now I've got to go read and digest more information. This is fun. Good stuff. – Ronnie Royston Feb 11 '18 at 18:45
  • 1
    Frank, I'm thinking this AdminSDK functionality should be available in the firebase.google.com web UI (the 'console'). In other words, I should be able to configure custom tokens, or multi-level-access (MLA) tokens, right in the console. I guess what I'm trying to say is a feature request. I'll have to do that. – Ronnie Royston Feb 11 '18 at 19:06
  • Agreed, that this sounds a really useful feature to have in the console (or in the CLI for that matter). Please [file a feature request](https://firebase.google.com/support/contact/bugs-features/). But in the meantime, the Admin SDK is the way to go. – Frank van Puffelen Feb 11 '18 at 19:16
  • I did, file a feature request. Thanks a million Frank – Ronnie Royston Feb 11 '18 at 19:59
1

Detailed How To: Firebase has what's called Custom Claims for this functionality as detailed in their Control Access with Custom Claims and Security Rules. Basically, you stand up a separate node server, install the Firebase AdminSDK:

npm install firebase-admin --save

Generate/Download a Private Key from the Service Accounts tab in the Firebase Console and put that on your node server. Then simply create a bare bones node app to assign Custom Claims against each UID (user) that you wish. Something like below worked for me:

var admin = require('firebase-admin');
var serviceAccount = require("./the-key-you-generated-and-downloaded.json");
admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: "https://xxxxxxxxxxxxxxxxxxxx.firebaseio.com"
});
admin.auth().setCustomUserClaims("whatever-uid-you-want-to-assign-claim-to", {admin: true}).then(() => {
    console.log("Custom Claim Added to UID. You can stop this app now.");
});

That's it. You can now verify if the custom claim is applied by logging out of your app (if you were previously logged in) and logging back in after you update your web app's .onAuthStateChanged method:

firebase.auth().onAuthStateChanged(function(user) {
    if (user) { 
        firebase.auth().currentUser.getIdToken()
            .then((idToken) => {
                // Parse the ID token.
                const payload = JSON.parse(window.atob(idToken.split('.')[1]));
                // Confirm the user is an Admin.
                if (!!payload['admin']) {
                    //showAdminUI();
                    console.log("we ARE an admin");
                }
                else {
                    console.log("we ARE NOT an admin");
                }
            })
            .catch((error) => {
                console.log(error);
            });
    }
    else {
        //USER IS NOT SIGNED IN
    }
});
Ronnie Royston
  • 16,778
  • 6
  • 77
  • 91