0

Before allowing a user to register, I want to do a database check to see if the username already exists. Tried following this and this StackOverflow post.

I expected the user would not be able to log in, however, the user is still allowed to log in :(

Register.js

import React, { useContext, useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import firebase from '../firebase';
import { UserContext } from '../userContext';

export const Register = () => {
  const navigate = useNavigate();

  const [emailAddress, setEmailAddress] = useState('');
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');

  const currentUser = useContext(UserContext);

  const handleSubmit = async event => {
    event.preventDefault();
    try {
      await firebase
        .auth()
        .createUserWithEmailAndPassword(emailAddress, password);
      navigate('/');
    } catch {}
  };

  useEffect(() => {
    (async () => {
      if (currentUser) {
        await currentUser?.updateProfile({
          displayName: username,
        });
        const usernamesRef = firebase.database().ref('usernames');
        const newUsername = {
          [username]: currentUser.uid,
        };
        usernamesRef.push(newUsername);
        const usersRef = firebase.database().ref('users');
        const newUser = {
          [currentUser.uid]: {
            email: emailAddress,
            username,
          },
        };
        usersRef.push(newUser);
      }
    })();
  }, [currentUser, emailAddress, username]);

  useEffect(() => {
    if (currentUser) {
      navigate('/');
    }
  }, [currentUser, navigate]);

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <label htmlFor="email-address">Email Address: </label>
        <input
          id="email-address"
          onChange={event => setEmailAddress(event.target.value)}
          value={emailAddress}
        />
        <label htmlFor="username">Username: </label>
        <input
          id="username"
          onChange={event => setUsername(event.target.value)}
          value={username}
        />
        <label htmlFor="password">Password: </label>
        <input
          id="password"
          onChange={event => setPassword(event.target.value)}
          type="password"
          value={password}
        />
        <button type="submit">Register</button>
      </form>
    </div>
  );
};

Firebase Realtime Database rules based on this StackOverflow answer:

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null",
    "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"
        }
      }
    }
  }
}

Repo for reference

jaypee
  • 1,757
  • 3
  • 9
  • 12
  • 1
    Please note: `firebase.auth().createUserWithEmailAndPassword(emailAddress, password);` - this will not only try to create the new user, but also will automatically login the user, if the attempt to create succeeds. Further, I wonder why the `catch { }` block is left empty! It'd be useful to handle the error, if the `createUserWithEmailAndPassword` fails (for an already existing user email in Firebase Authentication). – Neelavar Mar 30 '20 at 04:32
  • When the user registers, I ask for email, username, and password and then set the username as the `displayName` on the Firebase user object. Now, when a subsequent user registers, I want to make sure that the username does not already exist. I understand that Firebase already checks for duplicate emails automatically and throws an error. However, I need to check for duplicate usernames before the user can register. The `catch` block is empty for now cause I am still prototyping. – jaypee Mar 30 '20 at 04:41
  • 1
    Oh Ok. Typically the `displayName` property on the `User` object need not be unique, hence irrespective of what the subsequent user registers with say, an already existing `displayName` (however with a unique email ID), the `createUser..` method still succeeds and logs the user in. In order to avoid this, you may have to first query the list of _already registered users_ and check for any duplication in `displayName` (since you wish to use this property for `username`) and only if the `displayName` is unique, then you can continue to call `createUser...` method. – Neelavar Mar 30 '20 at 04:48
  • Right, so I tried that but the user needs to be signed in to be able to query the database, otherwise I get a permissions error. Need some kind of workaround to allow a narrow query from a user that isn’t authenticated without sacrificing security. – jaypee Mar 30 '20 at 05:51

0 Answers0