/ Firebase

[Updated] Simple Login in Firebase Web API

Simple Login in Firebase with Web API
The Internet has become a new World now. And its also dicey in itself in terms of its usage, it has its own faces. Authentication and Authorization are the primary ways to maintain integrity. We all know about the how-to’s of these in Web Apps. Today we will see basic authentication i.e. Simple Login in Firebase. As it is a Database-as-a-Service, it also supplies an authentication system which can be leveraged for basic needs application.

Edit:

Auth schemes in new Google Firebase:
https://time2hack.com/2016/06/auth-schemes-in-google-firebase/

For quick view on what we are building here, the sweet demo and download links for this Contact Store App are:

Demo Download

In the last post we had already discussed about the introduction of Firebase and setting up a quic application with Firebase. Lets equip our last app i.e. Contact Store Application with Authentication.

So before moving ahead, lets see how the data is stored in Firebase; as in last post we saw a sample data like this:

{
  "contacts" : {
    "-Jl4Y4ek1Gpb1dLW9R8o" : {
      "email" : "pankaj@time2hack.com",
      "location" : {
        "city" : "Gurgaon",
        "state" : "Haryana",
        "zip" : "122001"
      },
      "name" : "Pankaj Patel"
    },
    ...
  }
}

Some notes on Firebase DB:

  • The DB is NoSQL based
  • Data is written JSON
  • Every JSON Object represents a node in a Tree
  • A Node may or may not have child nodes
  • Firebase avoids arrays, because of possible key collision on concurrent access of DB Data
  • Arrays are replaced by Objects of key and value pairs
  • Keys in collection are generated by Firebase e.g. -Jl4Y4ek1Gpb1dLW9R8o

So if the DB has (suppose) children till 5th level, and we are accessing any third level child; we will receive all the data which it possess along with all its children.

So we need to plan the application and the DB structure accordingly. And it totally depends on following two things:

  1. Primary Purpose
  2. Good to Have Features

So for now we are not planning any Contact Sharing, every user is having control on its data and there is no intersection among the users. So as per this plan we can create users as immediate child of DB and contacts will be the child of users. Now it looks like this:

{
  "contactdb" :{
    "profiles" : {
      "1" : {
        "email" : "pankaj@time2hack.com",
        "location" : {
          "city" : "Gurgaon",
          "state" : "Haryana",
          "zip" : "122001"
        },
        "name" : "Pankaj Patel"
      }
    },
    "contacts" : {
      "1" : {
        "-Jl9cQXgm_6YkdC1xgCK" : {
          "name" : "Pankaj",
          "email" : "pankaj@pankaj.pro",
          "location" : {
            "city" : "Gurgaon",
            "state" : "Haryana",
            "zip" : "122001"
          },
        },
      },
    }
  }
}

For this simple login, we need to enable Email and Password authentication scheme in our Firebase DB.

After that we need to setup the security rules for our DB. It is very critical and important to setup the security rules which works with the authentication scheme and DB structure. As we have decided the DB structure, we would write the security rules as follows. It will have the combination of .read, .write, $variables, $validations and some session constants like auth.

{
  "rules": {
    "profiles": {
      "$uid": {
        // grants write access to the owner of this user account
        // whose uid must exactly match the key ($uid)
        ".write": "auth !== null && auth.uid === $uid",

        // grants read access to any user who is logged in
        // with an email and password
        ".read": "auth !== null && auth.provider === 'password'"
      }
    },
    "conatcts":{
      "$uid": {
        // grants write access to the owner of this user account
        // whose uid must exactly match the key ($uid)
        ".write": "auth !== null && auth.uid === $uid",

        // grants read access to any user who is logged in
        // with an email and password
        ".read": "auth !== null && auth.provider === 'password'"
      }
    }
  }
}

Short notes on Firebase Security Rules:

  • All the rules will be listed under a global object.
  • ruleswill be the only child in Global Object
  • .read and .write are used to set the readability and write-ability of any object
  • .read and .write can be set to true OR false
  • .validate can check what type of data can be written
  • .validate can also be used to block the writes
  • .validate can’t check for deletions
  • auth is a global variable holding the provider and uid information of user
  • provider can be password, facebook, github etc.
  • uid is auth scheme based i.e. simplelogin:1, facebook:2, github:3 etc.

There’s more that we can do with the firebase security rules. You can get full info on firebase security rules here.

The profiles and contacts are our data storage areas, and with .read and .write we can specify who should be allowed to read and write data at particular object. Below the profiles or contacts object, we have specified $uid variable to indicate a user logging in, where auth will hold the basic authentication info like the userId and provider. In our Simple Login scheme, provider is password.

For login, we need to create users. We can create a new user by createUserWithEmailAndPassword method on our DB reference. This method can be used in following way:

var data = {
  email: $('#registerEmail').val(), //get the email from Form
  password : $('#registerPassword').val() //get the pass from Form
};
firebase
  .auth()
  .createUserWithEmailAndPassword(data.email, data.password)
  .then( function(user){
    console.log("Successfully created user account with uid:", user.uid);
  })
  .catch(function(error){
    console.log("Error creating user:", error);
  });

In createUserWithEmailAndPassword, we accomplished following things sequentially:

  • Created a user with supplied email and password
  • Logged the user in. This is explained further.
  • Saved the extra info in profiles collection in out DB

Now to login with Simple Login scheme in Firebase, authWithPassword method can be used in DB Reference.

vatr data = {
  email    : $('#loginEmail').val(),
  password : $('#loginPassword').val()
};
var auth = null;
firebase
  .auth()
  .signInWithEmailAndPassword(data.email, data.password)
  .then( function(user){
    console.log("Authenticated successfully with payload:", user);
    auth = user;
  })
  .catch(function(error){
    console.log("Login Failed!", error);
  });

Now, we have a successful registration and login, we can operate the DB as per our desired data needs. Here’s the full code to the sample Contacts Store app created with the Simple Login in Firebase. In following code, there is some code to make the UI work like a real application.

Demo and download links for this Contact Store App:

Demo Download

Here is the HTML for our final Contact Store Application:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Time to Hack: Simple Login with Firebase Web API</title>
  <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
  <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
  <style type="text/css">
    #contacts p, #contacts p.lead{ margin: 0; }
    .authenticated{ display: block;margin-top: 20px;margin-bottom: 10px;}
    .unauthenticated{display: none;}
    .modal-header:last-child{border-bottom: 0;}
  </style>
</head>
<body>
  <div class="container">
    <span class="userAuth authenticated pull-right">
        <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#registerModal">Register</button>
        <button type="button" class="btn btn-success" data-toggle="modal" data-target="#loginModal">Login</button>
    </span>
    <h1>Contact Store Application</h1>
    <hr/>
    <div class="row unauthenticated">
      <center>
        <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#addContactModal">Add Contact</button>
      </center>
      <div class="col-md-6">
        <ul id="contacts" class="list-group">
          <!-- Conatct Object li.list-group-item.contact will be added here by js -->
        </ul>
      </div>
    </div>
  </div>
  <div class="modal fade" id="registerModal" tabindex="-1" role="dialog" aria-labelledby="Register" aria-hidden="true">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
          <h4 class="modal-title" id="registerModalLabel">Register</h4>
        </div>
        <div class="modal-body">
          <div class="form-group">
            <label for="recipient-name" class="control-label">First Name:</label>
            <input type="text" class="form-control" id="registerFirstName">
          </div>
          <div class="form-group">
            <label for="recipient-name" class="control-label">Last Name:</label>
            <input type="text" class="form-control" id="registerLastName">
          </div>
          <div class="form-group">
            <label for="recipient-name" class="control-label">Email:</label>
            <input type="text" class="form-control" id="registerEmail">
          </div>
          <div class="form-group">
            <label for="message-text" class="control-label">Password:</label>
            <input type="password" class="form-control" id="registerPassword">
          </div>
          <div class="form-group">
            <label for="message-text" class="control-label">Confirm Password:</label>
            <input type="password" class="form-control" id="registerConfirmPassword">
          </div>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
          <button type="button" class="btn btn-primary" id="doRegister">Register</button>
        </div>
      </div>
    </div>
  </div>
  <div class="modal fade" id="loginModal" tabindex="-1" role="dialog" aria-labelledby="Login" aria-hidden="true">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
          <h4 class="modal-title" id="loginModalLabel">Login</h4>
        </div>
        <div class="modal-body">
          <div class="form-group">
            <label for="recipient-name" class="control-label">Email:</label>
            <input type="text" class="form-control" id="loginEmail">
          </div>
          <div class="form-group">
            <label for="message-text" class="control-label">Password:</label>
            <input type="password" class="form-control" id="loginPassword">
          </div>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
          <button type="button" class="btn btn-primary" id="doLogin">Login</button>
        </div>
      </div>
    </div>
  </div>
  <div class="modal fade" id="addContactModal" tabindex="-1" role="dialog" aria-labelledby="Add Contact" aria-hidden="true">
    <div class="modal-dialog">
      <div class="modal-content">
        <form method="post" name="contactForm">
        <div class="modal-header">
          <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
          <h4 class="modal-title" id="addContactModalLabel">Add Contact</h4>
        </div>
        <div class="modal-body">
            <div class="form-group">
              <label for="name">Name</label>
              <input type="text" class="form-control" id="name" required placeholder="Enter name">
            </div>
            <div class="form-group">
              <label for="email">Email</label>
              <input type="email" class="form-control" id="email" required placeholder="Enter Email">
            </div>
            <h3>Location</h3>
            <div class="form-group">
              <label for="city">City</label>
              <input type="text" class="form-control" id="city" placeholder="Enter City">
            </div>
            <div class="form-group">
              <label for="email">State</label>
              <input type="text" class="form-control" id="state" placeholder="Enter State">
            </div>
            <div class="form-group">
              <label for="zip">Zip</label>
              <input type="text" class="form-control" id="zip" placeholder="Enter Zip Code">
            </div>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
          <button type="submit" class="btn btn-primary addValue">Submit</button>
        </div>
        </form>
      </div>
    </div>
  </div>
  <div class="modal fade" id="messageModal" tabindex="-1" role="dialog" aria-labelledby="Message" aria-hidden="true">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
          <h4 class="modal-title" id="messageModalLabel">Message</h4>
        </div>
        <div class="modal-footer">
          <div class="pre-auth">
            <button type="button" class="btn btn-default pull-left" data-dismiss="modal">Close</button>
            <span class="">
              <button type="submit" class="btn btn-primary" data-dismiss="modal" data-toggle="modal" data-target="#registerModal">Register</button>
              <button type="submit" class="btn btn-success" data-dismiss="modal" data-toggle="modal" data-target="#loginModal">Login</button>
            </span>
          </div>
          <div class="post-auth"></div>
        </div>
      </div>
    </div>
  </div>
  <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
  <!-- Latest compiled and minified Bootstrap -->
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
  <!-- Include Firebase Library -->
  <script src="https://www.gstatic.com/firebasejs/4.3.1/firebase.js"></script>
  <!-- Contacts Store JavaScript -->
  <script src="script.js"></script>
</body>
</html>

And the JavaScript for the app:

// file: script.js
$(document).ready(function(){
  //initialize the firebase app
  var config = {
    apiKey: "AIzaSyCKNcULQZxFMYioXei32XNWQVoeutz4XDA",
    authDomain: "contact-book-new.firebaseapp.com",
    databaseURL: "https://contact-book-new.firebaseio.com",
    projectId: "contact-book-new",
    storageBucket: "contact-book-new.appspot.com",
    messagingSenderId: "473268388365"
  };
  firebase.initializeApp(config);

  //create firebase references
  var Auth = firebase.auth(); 
  var dbRef = firebase.database();
  var contactsRef = dbRef.ref('contacts')
  var usersRef = dbRef.ref('users')
  var auth = null;

  //Register
  $('#doRegister').on('click', function (e) {
    e.preventDefault();
    $('#registerModal').modal('hide');
    $('#messageModalLabel').html(spanText('<i class="fa fa-cog fa-spin"></i>', ['center', 'info']));
    $('#messageModal').modal('show');
    var data = {
      email: $('#registerEmail').val(), //get the email from Form
      firstName: $('#registerFirstName').val(), // get firstName
      lastName: $('#registerLastName').val(), // get lastName
    };
    var passwords = {
      password : $('#registerPassword').val(), //get the pass from Form
      cPassword : $('#registerConfirmPassword').val(), //get the confirmPass from Form
    }
    if( data.email != '' && passwords.password != ''  && passwords.cPassword != '' ){
      if( passwords.password == passwords.cPassword ){
        //create the user
        
        firebase.auth()
          .createUserWithEmailAndPassword(data.email, passwords.password)
          .then(function(user){
            //now user is needed to be logged in to save data
            console.log("Authenticated successfully with payload:", user);
            auth = user;
            //now saving the profile data
            usersRef
              .child(user.uid)
              .set(data)
              .then(function(){
                console.log("User Information Saved:", user.uid);
              })
            $('#messageModalLabel').html(spanText('Success!', ['center', 'success']))
            //hide the modal automatically
            setTimeout(function() {
              $('#messageModal').modal('hide');
              $('.unauthenticated, .userAuth').toggleClass('unauthenticated').toggleClass('authenticated');
              contactsRef.child(auth.uid)
                .on("child_added", function(snap) {
                  console.log("added", snap.key(), snap.val());
                  $('#contacts').append(contactHtmlFromObject(snap.val()));
                });
            }, 500);
            console.log("Successfully created user account with uid:", user.uid);
            $('#messageModalLabel').html(spanText('Successfully created user account!', ['success']))
          })
          .catch(function(error){
            console.log("Error creating user:", error);
            $('#messageModalLabel').html(spanText('ERROR: '+error.code, ['danger']))
          });
      } else {
        //password and confirm password didn't match
        $('#messageModalLabel').html(spanText("ERROR: Passwords didn't match", ['danger']))
      }
    }  
  });

  //Login
  $('#doLogin').on('click', function (e) {
    e.preventDefault();
    $('#loginModal').modal('hide');
    $('#messageModalLabel').html(spanText('<i class="fa fa-cog fa-spin"></i>', ['center', 'info']));
    $('#messageModal').modal('show');

    if( $('#loginEmail').val() != '' && $('#loginPassword').val() != '' ){
      //login the user
      var data = {
        email: $('#loginEmail').val(),
        password: $('#loginPassword').val()
      };
      firebase.auth().signInWithEmailAndPassword(data.email, data.password)
        .then(function(authData) {
          console.log("Authenticated successfully with payload:", authData);
          auth = authData;
          $('#messageModalLabel').html(spanText('Success!', ['center', 'success']))
          setTimeout(function () {
            $('#messageModal').modal('hide');
            $('.unauthenticated, .userAuth').toggleClass('unauthenticated').toggleClass('authenticated');
            contactsRef.child(auth.uid)
              .on("child_added", function(snap) {
                console.log("added", snap.key(), snap.val());
                $('#contacts').append(contactHtmlFromObject(snap.val()));
              });
          })
        })
        .catch(function(error) {
          console.log("Login Failed!", error);
          $('#messageModalLabel').html(spanText('ERROR: '+error.code, ['danger']))
        });
    }
  });

  //save contact
  $('.addValue').on("click", function( event ) {  
    event.preventDefault();
    if( auth != null ){
      if( $('#name').val() != '' || $('#email').val() != '' ){
        contactsRef.child(auth.uid)
          .push({
            name: $('#name').val(),
            email: $('#email').val(),
            location: {
              city: $('#city').val(),
              state: $('#state').val(),
              zip: $('#zip').val()
            }
          })
          document.contactForm.reset();
      } else {
        alert('Please fill at-lease name or email!');
      }
    } else {
      //inform user to login
    }
  });
})
 
//prepare contact object's HTML
function contactHtmlFromObject(contact){
  console.log( contact );
  var html = '';
  html += '<li class="list-group-item contact">';
    html += '<div>';
      html += '<p class="lead">'+contact.name+'</p>';
      html += '<p>'+contact.email+'</p>';
      html += '<p><small title="' + contact.location.zip+'">'
            +contact.location.city + ', '
            +contact.location.state + '</small></p>';
    html += '</div>';
  html += '</li>';
  return html;
}

function spanText(textStr, textClasses) {
  var classNames = textClasses.map(c => 'text-'+c).join(' ');
  return '<span class="'+classNames+'">'+ textStr + '</span>';
}

Demo Download

Happy Coding…!