3

I have a react redux firebase app with a content management side (all those pages start with /admin/...). I need to restrict firebase database and firebase storage write access to a small subset of those users, as well as handle a redirect when an unauthenticated (or authenticated without admin role) user tries to go there. Currently, anyone who figures out that the auth buttons are at /admin can log in with facebook or google, then have access to everything.

From what I've read, it looks like one does this via custom tokens, but I'm unclear on where to put the snippets they provide to do this securely. I tried manually adding admin: true inside the firebase database via the web console, then set up my firebase database rules like so:

{
  "rules": {
    ".read": "true",
    ".write": "auth != null && root.child('users').child(auth.uid).child('admin').val() == true"
  }
}

This seems to work in terms of protecting write access to the database, but I was unable to use the same admin field/token for firebase storage, so I had to do this:

service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if request.auth!=null && request.auth.uid=="{my uid}";
      //request.auth.token.admin==true did not seem to work
    }
  }
}

I also couldn't get that admin: true to show up on the client, so I don't have a way of of redirecting right now.

Can someone please clear this up for me?

ZachO
  • 68
  • 9
  • 1
    Check this answer: https://stackoverflow.com/questions/36878040/how-do-i-set-up-roles-in-firebase-auth Firebase has an admin API to define custom claims securely. – bojeil Mar 10 '18 at 19:55
  • I guess I just don't know the proper place to set these claims in my app. I used create-react-app to scaffold the app, and it seems like there's no secure place to import firebase-admin. – ZachO Mar 12 '18 at 18:41
  • I ended up trying this in a firebase cloud function - one that triggers when a user is created (which isn't an applicable trigger, but was something I could control), and that put the claim in correctly and is blocking write access to the db and storage like I need (by editing the rules to check for the admin token). You can make this an answer and I'll accept it, but it might be nice to know where one would handle the server side logic with respect to React? – ZachO Mar 12 '18 at 18:48
  • Added answer below. – bojeil Mar 13 '18 at 01:59

2 Answers2

4

One way of limiting access to a small (and stable) subset of users is by using their uid and check it is contained in an array of admin users uids, as shown below:

function isAdminUser() {
   return request.auth.uid in [
        "UcqBSJOPDT........zzaeh1",
        "pQHSCZ1........hS2mfKmd2", 
        "MK8kif7.......eEqJzUl2n1", 
        "FrSm................XI82"
    ];
}

service firebase.storage {
  match /b/xxxxxxx.appspot.com/o {
    match /{allPaths=**} {
      allow write: .....;
      allow read: if request.auth != null && isAdminUser();
    }
  }
}
Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121
  • Is the suggested isAdminUser() just a list you manually keep inside your rules? – Wesley Barnes May 08 '21 at 09:59
  • @WesleyBarnes yes indeed. It is very basic, this is why I mention this approach is OK for a "small (and stable) subset of users". It is very basic but it is simpler than using Custom Claims as bojeil advises in his answer and which is definitely the way to go if your list of admin users is long or changes frequently. – Renaud Tarnec May 08 '21 at 10:06
2

Firebase supports role based access on any user via custom user claims on the ID token: https://firebase.google.com/docs/auth/admin/custom-claims

You would define the admin access rule:

{
  "rules": {
    "adminContent": {
      ".read": "auth.token.admin === true",
      ".write": "auth.token.admin === true",
    }
  }
}

Set the user role with the Admin SDK from a server. You could use your own server, or set this via an onCreate event in cloud functions (as long as you can tell who is admin and who is not from there), etc:

// Set admin privilege on the user corresponding to uid.
admin.auth().setCustomUserClaims(uid, {admin: true}).then(() => {
  // The new custom claims will propagate to the user's ID token the
  // next time a new one is issued.
});

This will propagate to the corresponding user's ID token claims.

To parse it from the token on the client, check: https://firebase.google.com/docs/auth/admin/custom-claims#access_custom_claims_on_the_client

You basically need to base64 decode the payload of the token. However, always rely on server side validation via Firebase rules or ID token verification if you are running your own server side code.

bojeil
  • 29,642
  • 4
  • 69
  • 76