Enforcing unique usernames with Firebase simplelogin

Solution 1:

There are two things to do here, a client-side check and a server-side rule.

At the client side, you want to check whether the username already exists, so that you can tell the user that their input is invalid, before sending it to the server. Where exactly you implement this up to you, but the code would look something like this:

var ref = new Firebase('https://YourFirebase.firebaseio.com');
var q = ref.child('profiles').orderByChild('username').equalTo(newUsername);
q.once('value', function(snapshot) {
  if (snapshot.val() === null) {
    // username does not yet exist, go ahead and add new user
  } else {
    // username already exists, ask user for a different name
  }
});

You can use this to check before writing to the server. However, what if a user is malicious and decides to use the JS console to write to the server anyway? To prevent this you need server-side security.

I tried to come up with an example solution but I ran into a problem. Hopefully someone more knowledgeable will come along. My problem is as follows. Let's say your database structure looks like this:

{
  "profiles" : {
    "profile1" : {
      "username" : "Nick",
      "md5_hash" : "..."
    },
    "profile2" : {
      "username" : "Marein",
      "md5_hash" : "..."
    }
  }
}

When adding a new profile, you'd want to have a rule ensuring that no profile object with the same username property exists. However, as far as I know the Firebase security language does not support this, with this data structure.

A solution would be to change the datastructure to use username as the key for each profile (instead of profile1, profile2, ...). That way there can only ever be one object with that username, automatically. Database structure would be:

{
  "profiles" : {
    "Nick" : {
      "md5_hash" : "..."
    },
    "Marein" : {
      "md5_hash" : "..."
    }
  }
}

This might be a viable solution in this case. However, what if not only the username, but for example also the email has to be unique? They can't both be the object key (unless we use string concatenation...).

One more thing that comes to mind is to, in addition to the list of profiles, keep a separate list of usernames and a separate list of emails as well. Then those can be used easily in security rules to check whether the given username and email already exist. The rules would look something like this:

{
  "rules" : {
    ".write" : true,
    ".read" : true,
    "profiles" : {
      "$profile" : {
        "username" : {
          ".validate" : "!root.child('usernames').child(newData.val()).exists()"
        }
      }
    },
    "usernames" : {
      "$username" : {
        ".validate" : "newData.isString()"
      }
    }
  }
}

However now we run into another problem; how to ensure that when a new profile is created, the username (and email) are also placed into these new lists? [1]

This in turn can be solved by taking the profile creation code out of the client and placing it on a server instead. The client would then need to ask the server to create a new profile, and the server would ensure that all the necessary tasks are executed.

However, it seems we have gone very far down a hole to answer this question. Perhaps I have overlooked something and things are simpler than they seem. Any thoughts are appreciated.

Also, apologies if this answer is more like a question than an answer, I'm new to SO and not sure yet what is appropriate as an answer.

[1] Although maybe you could argue that this does not need to be ensured, as a malicious user would only harm themselves by not claiming their unique identity?