How to Include a Username When Storing Email and Password Using Firebase (Baas) in an Android App

Use a username as key in firebase for storing users

Update

As pointed out in the comments since users are being signed up using username + '@domain.com' Firebase will prevent duplicate usernames because it won't allow two separate users to sign up with the same email. Once your user is created you can write the username to the database and there won't be any collisions. Keep in mind that this problem can become more difficult if users are allowed to change their username. You'll have to check the newly requested username against the existing usernames to determine if the requested name already exists. Lastly, don't forget Firebase is case sensitive so you might want to cast all usernames to lower/uppercase and trim the trailing white space before you upload them.

The biggest problem I can think of is that uid's are guaranteed to be unique by Firebase so there can't be any duplicates. If you store everything by username you'll have to make sure there are no duplicate usernames when users sign up. So you have to make an area of the database that can be read from by unauthenticated users to check the requested username against existing usernames.

You have to think about how you're going to access the data most of the time. Additionally, if you denormalize the user data (store it under uid & username) you have to make sure that both copies stay in sync. It's probably easier to just store username -> uid so that you have a mapping to get to the user data if you only have the username. This isn't going to happen that often and the extra nested query isn't going to make much of a performance difference.

Firebase login and signup with username

Try this:

auth.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener(SignupActivity.this, new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
Toast.makeText(SignupActivity.this, "createUserWithEmail:onComplete:" + task.isSuccessful(), Toast.LENGTH_SHORT).show();
progressBar.setVisibility(View.GONE);
generateUser(email, password)

if (!task.isSuccessful()) {
Toast.makeText(SignupActivity.this, "Authentication failed." + task.getException(),
Toast.LENGTH_SHORT).show();
} else {
startActivity(new Intent(SignupActivity.this, MainActivity.class));
finish();
}
}
});

This is the method that is being called above.

public void generateUser(String username, String password)
{

FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference users = database.getReference("users"); //users is a node in your Firebase Database.
User user = new User(username, password); //ObjectClass for Users
users.push().setValue(user);

}

Also:
User.class

   public class User {

String username;
String password;

public User() {
//Empty Constructor For Firebase
}

public User(String username, String password)
{
this.username = username; //Parameterized for Program-Inhouse objects.
this.password = password;
}

//Getters and Setters
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
}

Can I make Firebase use a username login process?

DISCLAIMER: This code is now over two years old. While this doesn't mean it's deprecated, I would strongly recommend investigating alternative methods before assuming this is the best approach. I personally wouldn't want the standard login process for Firebase to be dictated by my initial approach to a problem while Firebase wasn't as heavily adopted as it is now.



After multiple iterations of development I've come up with the following design to address this. I will post my code snippets in Swift, but they will typically be translatable directly into Android with ease.


  1. Create a Firebase registration process for email/password.

This is required as the backbone of the user's sign-in experience. This can be implemented completely from the stock Firebase API documentation provided here



  1. Prompt the user to enter their username

The username entry should be completed at registration, I'd recommend an additional field in the registration flow. I also recommend checking if the user has a username whenever they log in. If they don't, then display a SetUsername interface that prompts them to set a username before progressing further into the UI. A user might not have a username for a few reasons; it could be revoked for being rude or reserved, or they might have signed up prior to the username being required at registration.

Make sure that if you're using a persistence-enabled Firebase build that you use Firebase Transactions. The transactions are necessary, otherwise your app can make assumptions about the data in the username table, even though a username might have been set for a user only seconds earlier.

I would also advise enforcing the username to be nearly alphanumeric (I allow for some harmless punctuation). In Swift I can achieve this with the following code:

static var invalidCharacters:NSCharacterSet {
let chars = NSMutableCharacterSet.alphanumericCharacterSet()

// I add _ - and . to the valid characters.
chars.addCharactersInString("_-.")

return chars.invertedSet
}

if username.rangeOfCharacterFromSet(invalidCharacters) != nil {
// The username is valid
}


  1. Saving the user data

The next important step is knowing how to save the user's data in a way that we can access it in the future. Below is a screenshot of the way I store my user data:
User details hierarchy
A few things to note:

  • The usernames are stored twice, once in usernames and again in details/[uid]/username. I recommend this as it allows you to be case sensitive with usernames (see the next point) and it also allows you to know the exact database reference to check a username (usernames/scooby) rather than having to query or check through the children of details to find a username that matches (which would only become more complicated when you have to factor in case-sensitivity)
  • the usernames reference is stored in lowercase. When I check the values in this reference, or when I save to this reference, I ensure that I only save data in lowercase. This means if anyone wants to check if the username 'scoobY' exists, it will fail because in lowercase it's the same username as the existing user "Scooby".
  • The details/[uid]/username field contains capitals. This allows for the username to display in the case of preference for the user, rather than enforcing a lowercase or Capitalised word, the user can specify their name as "NASA Fan" and not be converted over to "Nasa Fan", while also preventing anyone else from registering the username "NASA FAN" (or any other case iterations)
  • The emails are being stored in the user details. This might seem peculiar because you can retrieve the current user's email via Firebase.auth().currentUser.email?. The reason this is necessary is because we need references to the emails prior to logging in as the user.


  1. Logging in with email or username

For this to work seamlessly, you need to incorporate a few checks at login.

Since I've disallowed the @ character in usernames, I can assume that a login request containing an @ is an email request. These requests get processed as normal, using Firebase's FIRAuth.auth().signInWithEmail(email, password, completion) method.

For all other requests, we will assume it's a username request. Note: The cast to lowercase.

let ref = FIRDatabase.database().reference()
let usernameRef = ref.child("users/usernames/\(username.lowercaseString)")

When you perform this retrieval, you should consider if you have persistence-enabled, and if there's a possibility that a username could be revoked. If a username could be revoked and you have persistence-enabled, you will want to ensure you retrieve the username value within a Transaction block, to make sure you don't get a cached value back.

When this retrieval succeeds, you get the value from username[username], which is the user's uid. With this value, you can now perform a retrieval on the user's email value:

let ref = FIRDatabase.database().reference()
let usernameRef = ref.child("users/details/[uid]/email")

Once this request succeeds, you can then perform the standard Firebase email login with the email string you just retrieved.

The exact same retrieval methods can be used to retrieve an email from a username to allow for password recovery.

A few points to be wary of for advanced functionality:
- If you allow the user to update their email using FIRUserProfileChangeRequest, make sure you update it both on the auth AND the details[uid]email field, otherwise you will break the username login functionality
- You can significantly reduce the code required to handle all the different failure cases in the retrieval methods by using success and failure blocks. Here's an example of my get email method:

static func getEmail(username:String, success:(email:String) -> Void, failure:(error:String!) -> Void) {
let usernameRef = FIRDatabase.database().reference().child("users/usernames/\(username.lowercaseString)")

usernameRef.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
if let userId = snapshot.value as? String {
let emailRef = FIRDatabase.database().reference().child("users/details/\(userId)/email")

emailRef.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
if let email = snapshot.value as? String {
success(email: email)
} else {
failure(error: "No email found for username '\(username)'.")
}
}) { (error) in
failure(error: "Email could not be found.")
}
} else {
failure(error: "No account found with username '\(username)'.")
}
}) { (error) in
failure(error: "Username could not be found.")
}
}

This success/failure block implementation allows the code I call in my ViewControllers to be much cleaner. Å login calls the following method:

if fieldText.containsString("@") {
loginWithEmail(fieldText)
} else {
// Attempt to get email for username.
LoginHelper.getEmail(fieldText, success: { (email) in
self.loginWithEmail(email)
}, failure: { error in
HUD.flash(.Error, delay: 0.5)
})
}

How to add additional information to firebase.auth()

As far as I know, you have to manage the users profiles by yourself if you want to have more fields than the default user provided by Firebase.

You can do this creating a reference in Firebase to keep all the users profiles.

users: {
"userID1": {
"name":"user 1",
"gender": "male"
},
"userID2": {
"name":"user 2",
"gender": "female"
}
}

You can use onAuthStateChanged to detect when the user is logged in, and if it is you can use once() to retrieve user's data

firebaseRef.child('users').child(user.uid).once('value', callback)

Hope it helps



Related Topics



Leave a reply



Submit