39

Parse will shut down at the end of the year, so I decided to start using Firebase. I need to implement a register process with 3 fields : email, username, password (Email & username must be unique for my app).

Since, Firebase is not providing an easy way to manage username like Parse, I decided to use only the email/password registration and save some additional data like username. Here is my users data structure :

app : {
    users: {
       "some-user-uid": {
            email: "test@test.com"
            username: "myname"
       }
    }
}

But, what I want to do is to make the username unique and to check it before creating an account. These are my rules :

{
    "rules": {
        ".read": true,
        ".write": true,
        "users": {
            "$uid": {
                ".write": "auth !== null && auth.uid === $uid",
                ".read": "auth !== null && auth.provider === 'password'",
                "username": {".validate": "!root.child('users').child(newData.child('username').val()).exists()"}
            }
        }
   }
}

Thank you very much for your help

FloGz
  • 528
  • 1
  • 5
  • 12
  • So you want the Firebase rules to be checked on the Android client? – Da-Jin Feb 06 '16 at 17:27
  • Yes, I want to check first if username is taken if firebase tell me no then create an account otherwise just ask the user to pick another username – FloGz Feb 06 '16 at 17:34
  • 1
    Aside from my answer about data structure and security rules, here are some questions where the topic has been covered before: http://stackoverflow.com/questions/29970681/enforcing-unique-usernames-with-firebase-simplelogin, http://stackoverflow.com/questions/25294478/how-do-you-prevent-duplicate-user-properties-in-firebase, http://stackoverflow.com/questions/15910165/usernames-with-firebase-simple-login-email-password, http://stackoverflow.com/questions/20260476/what-firebase-rule-will-prevent-duplicates-in-a-collection-based-on-other-fields – Frank van Puffelen Feb 06 '16 at 18:24

4 Answers4

65

Part of the answer is to store an index of usernames, that you check against in your security rules:

app : {
    users: {
       "some-user-uid": {
            email: "test@test.com"
            username: "myname"
       }
    },
    usernames: {
        "myname": "some-user-uid"
    }
}

So the usernames node maps a username to a uid. It essentially reads as "username 'myname' is owned by 'some-user-uid'".

With this data structure, your security rules can check if there is already an entry for a given username:

"users": {
  "$uid": {
    ".write": "auth !== null && auth.uid === $uid",
    ".read": "auth !== null && auth.provider === 'password'",
    "username": {
      ".validate": "
        !root.child('usernames').child(newData.val()).exists() ||
        root.child('usernames').child(newData.val()).val() == $uid"
    }
  }
}

This validates that the username isn't claimed by anyone yet OR it is claimed by the current user.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • 1
    great answer, duplicative with http://stackoverflow.com/questions/25294478/how-do-you-prevent-duplicate-user-properties-in-firebase – Creos May 24 '16 at 15:24
  • 6
    Hi Frank, I think it would also be important to note that you should probably cast all checks to `usernames` to lowercase and store lowercase, that way you can't have identical usernames of varying case. The value stored in the `users/some-user-uid/username` field can be case sensitive, that can be the readable version, which should allow for a more user friendly username selection and case adherence. – Chris Conway May 31 '16 at 09:55
  • @Frank van Puffelen can you confirm what Viv is saying on his answer about using a Transection instead of a normal set query? And also, any regex you've written before that can be used to validate allowed characters for user names? – Relm Sep 14 '16 at 20:57
  • Thanks for your awesome answer! It really helped me. But there is still a problem I guess.. In your current implementation, the same user can claim as much usernames as he wants (if they are not taken). So he can basically create username1 till usernameN. How can we achieve, that the old username gets deleted before he claims his new one? – jdstaerk Sep 21 '16 at 18:51
  • Have an inverted data structure that maps `uid` to `username` and then verify that `uid` -> `username` === `username` -> `uid`. – Frank van Puffelen Sep 21 '16 at 22:26
  • @DDerTyp to avoid leaving behind old usernames that are no longer used do the following updates as a transaction (inherent in the multi-update feature of firebase) ` Simultaneous updates made this way are atomic: either all updates succeed or all updates fail.` https://firebase.google.com/docs/database/web/read-and-write update1: remove entry from usernames/$old_username:$userid update2: update entry in users/$userid/username:$new_username update3: add entry in usernames/$new_username:$userid – Tope Feb 28 '17 at 23:30
  • Thanks, will the validation give back a response of which field failed validation? Otherwise you could not give a meaningful response in the UI to the user and the check would have to be separate – Dominic Jun 08 '17 at 13:07
  • Use a transaction to update the username and you'll be certain what happens. – Frank van Puffelen Jun 08 '17 at 13:12
  • @FrankvanPuffelen Your answer is great. But would this structure help me implement a username and email login structure? Wouldnt it be better if I stored username : { some_username : some email} so that I can retreive the user's email and log him in. Also if my question is correct, what security rules would I use to make sure that it is only readable and writable during username creation? – Newbie Dec 15 '17 at 22:20
7

Save usernames as suggested by Frank but when you save usernames, use runTransaction function in Firebase to make sure that the username is not taken. This function is guaranteed by Firebase to be an atomic operation so you can be rest assured of no collision

firebaseRef.child("usernames").child(username).runTransaction(new Transaction.Handler() {
    @Override
    public Transaction.Result doTransaction(MutableData mutableData) {
        if (mutableData.getValue() == null) {
            mutableData.setValue(authData.getUid());
            return Transaction.success(mutableData);
        }

        return Transaction.abort();
    }

    @Override
    public void onComplete(FirebaseError firebaseError, boolean commited, DataSnapshot dataSnapshot) {
        if (commited) {
            // username saved
        } else {
            // username exists
        }
    }
});
Viven
  • 627
  • 7
  • 14
0

make a new branch for username and when new user login get list of all username and check wether it is present in db or not if it is present show them toast otherwise put its username in the username branch ..

-5

I dont know much about firebase security yet, but I may have solved the problem using Java. I have posted it below.

my data structure is

myapp
{
  users: {
          <unique generated-id>
          { username: "example.username" }
}
}


public boolean isUsernameExists(final String enteredUsername) {
        final Boolean[] isExist = {false};
        FBref.child("users").addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                for (DataSnapshot userSnapshot : dataSnapshot.getChildren()) {
                    String existingUsername = (String) userSnapshot.child("userName").getValue();
                    if (existingUsername.equals(enteredUsername)) {
                        isExist[0] = true;
                    }
                }
            }
            @Override
            public void onCancelled(FirebaseError firebaseError) {
                //some error thrown here
            }
        });
        return isExist[0];
    }