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: "[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

3

4 Answers 4

66

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.

Sign up to request clarification or add additional context in comments.

9 Comments

great answer, duplicative with stackoverflow.com/questions/25294478/…
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.
@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?
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?
Have an inverted data structure that maps uid to username and then verify that uid -> username === username -> uid.
|
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
        }
    }
});

1 Comment

You set it in the username list but not in the uid. Should we add a line on complete to do that?
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 ..

Comments

-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];
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.