简体   繁体   中英

Firebase user account security with firebaseSimpleLogin

I'm trying to find the best way to design security for our app that uses firebase

Basic problem

We want our users' data to be secure. We don't want a malicious agent to be able to access other users' private data on the Firebase db. It seems that there should be a solution for this via firebaseSimpleLogin, but despite scouring the documentation, we haven't seen one.

Problem specifics

  • We offer an app with user accounts, and these users have private data
  • Users should only be able to read:
    • their own data
    • app-wide data relevant to all users, eg a template that all users get a copy of when they initially create their account, the original copy of which is on the fb db
    • a portion of data of another user, if that other user has explicitly decided to share it with them eg a game they made that they want another user to have a copy of
  • Right now, users log in with FirebaseSimpleLogin. This is problematic because any malicious user can create their own account legitimately, and use their account's e-mail and password to login with a malicious script, and access the db

Solutions we've considered

1. Store a user_secret to ensure user has legitimate access

  • Inspired by 2nd method in answer to How to setup Firebase security rules to accept connections only from an iOS App
    • The structure would look like security->user_secret->associated_user OR security->user->{all_valid_user_secrets}
    • Security rule: ".read": "root.child('user_secrets/'+auth.uid).exists()"
  • We could store multiple user secret keys per user, allowing access from multiple verified sources (iOS app, web-app, etc)

Problems with #1

  • How do we restrict write/read access to the security child?
    • SimpleLogin doesn't exist for servers
    • We don't want this information visible, as a malicious user could technically read it to find info about his/her own account, and then use that to peruse the rest of the db
  • Same problem as in the problem statement: a user can generate an account legitimately, and then use those credentials to gain malicious access to the db

2. Temporarily Store User Secret

  • User initiates login
  • Node server generates password, stores it in restricted security child in Firebase (the server would be able to do this, as Firebase Secret allows full access)
  • We authorize firebase client side using Firebase SimpleLogin as we have been
  • The user interacts with the app. Firebase security rules only allow read/write access if the security child written by the node server is present
  • User initiates logout/crashes/closes app
  • Node server removes password from restricted security child
  • Unauthorize Firebase ref as we have been
  • Done

Problem with #2 - The issue with this method is the user is vulnerable while they're logged in, as their security information would be present.

3. Use built in Firebase Security Rules

  • We hoped there was a built in firebase solution, but haven't found one that solves the resolves the above problem. If you can point us to one that would be terrific.

We are hoping someone can help shed light on the best approach here, either using our ideas or another route. Thanks so much for your help.

You've essentially asked for someone to write an entire security schema for your app. It would be better if you understood security rules thoroughly before attempting to apply them to a complex structure like this. A good study of the docs from start to end would take you a long way to a fully functional solution.

Let's just focus on what seems to be the core problem, which is that you aren't sure how to make invites work securely with a client-only solution. (The node.js solution should also be obvious with this understanding, given the additional firepower provided by being able to create our own tokens) I'll make a lot of assumptions here; just apply these ideas to your current use case as you see fit.

A data structure:

/invites/$game_id/$uuid/true  (a place to store invited users)
/accepted_invites/$gameid/$userid/$uuid/true (a place to store accepted invites)
/games/$game_id (the place we want to invite users into)
/users/$user_id (a place where we put profiles for existing users)

1) When a new user creates an account in the app, write their profile into /users. Secure users/ as follows:

"users": {
   "$userid": {
      ".write": "auth.uid === $userid"
   }
}

2) To invite a user, create a uuid , which represents an unguessable id, and store it in invites/$game_id. Note that nobody should be able to read this path.

"invites": {
   "$game_id": {
     "$invite_id": {
        // I can only create an invite for groups I'm a member of
        ".write": "root.child('games/'+$game_id+'/members/'+auth.uid).exists()",
        ".validate": "newData.val() === true"
     }
   }
}

3) To join a game, a user must first accept the access token, which proves that they know the token (since they can't read the invites path) and links the token to their account id. The value of this entry is the uuid of the invite.

"accepted_invites": {
   "$game_id": {
      "$user_id": {
         ".write": "auth.uid === $user_id",
         ".validate": "root.child('invites/'+$game_id+'/'+newData.val()).exists()"
      }
   }
} 

4) A user can write themselves into a game if they have accepted an invite or when it is initially created and there are no members yet (the !data.parent().exists() rule)

"game": {
   "$gid": {
      "members": {
        "$uid": {
           ".write": "auth.uid === $uid",
           // I can join a group if a) I'm creating it or b) I have accepted an invite
           ".validate": "!data.parent().exists() || root.child('accepted_invites/'+$gid+'/'+auth.uid).exists()"
        }
      }
   }
}

Another enhancement to our client-side solution would be to assign the invites a priority, which represents when they expire, and then reference that priority in the security rules to control how long the token is valid.

I ended up going with idea #3, thanks to the help of @Kato's suggestions. The solution of using the built in rules and designing a schema allowed us to avoid needing a 3rd party auth server, so far. Some example of the schema was as follows:

 "game_detail" : {
      "$game_detail" : {
        ".read" : "data.child('owner').val() === auth.email || root.child('admins').val() === auth.email",
        ".write" : "newData.child('owner').val() === auth.email || root.child('admins').val() === auth.email"        
      }
    },

Then, the additional key that made #3 possible was that, in addition to having a schema of security rules, we also created a generic admin credential that can be used by anonymous users when they are logged in to access a subset of the DB and perform necessary cross account operations.

Thanks all for the input here.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM