Firebase android : make username unique

24,159

Solution 1

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: "[email protected]"
            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.

Solution 2

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
        }
    }
});
Share:
24,159
FloGz
Author by

FloGz

BY DAY : Android developer BY NIGHT : App developer FOR FUN : Football player, The Walking Dead Addict "When opportunity knocks, some people are in the backyard looking for four-leaf clovers" My app : https://play.google.com/store/apps/details?id=com.app.floriangz.droid404

Updated on November 04, 2021

Comments

  • FloGz
    FloGz over 2 years

    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: "[email protected]"
                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

  • Creos
    Creos almost 8 years
    great answer, duplicative with stackoverflow.com/questions/25294478/…
  • Chris Conway
    Chris Conway almost 8 years
    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.
  • user2997154
    user2997154 almost 8 years
    You set it in the username list but not in the uid. Should we add a line on complete to do that?
  • Relm
    Relm over 7 years
    @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?
  • jdstaerk
    jdstaerk over 7 years
    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?
  • Frank van Puffelen
    Frank van Puffelen over 7 years
    Have an inverted data structure that maps uid to username and then verify that uid -> username === username -> uid.
  • Tope
    Tope about 7 years
    @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.` 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
  • Dominic
    Dominic almost 7 years
    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
  • Frank van Puffelen
    Frank van Puffelen almost 7 years
    Use a transaction to update the username and you'll be certain what happens.
  • Newbie
    Newbie over 6 years
    @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?