Simple Login in Firebase Web API
Authentication and Authorization are the primary ways to maintain the integrity of web apps. In this post, we will see basic authentication, i.e. Simple Login in Firebase Web API
Authentication and Authorization are the primary ways to maintain the integrity of the application data.
We all know about the how-tos of these Web Apps. Today we will see basic authentication, i.e. Simple Login in Firebase.
As a Database-as-a-Service, it also supplies an authentication system that can be leveraged for basic needs applications.
Edit:
Auth schemes in new Google Firebase:
time2hack.com/auth-schemes-in-google-firebase/
For a quick view of what we are building here, the sweet demo and download links for this Contact Store App are:
In the last post, we discussed the introduction of Firebase and setting up a quick application with Firebase. Let's equip our last app i.e. Contact Store Application
with Authentication.
So before moving ahead, let's see how the data is stored in Firebase; as in the last post, we saw sample data like this:
{
"contacts" : {
"-Jl4Y4ek1Gpb1dLW9R8o" : {
"email" : "[email protected]",
"location" : {
"city" : "Gurgaon",
"state" : "Haryana",
"zip" : "122001"
},
"name" : "Pankaj Patel"
},
...
}
}
Some notes on Firebase Realtime DB
- It is NoSQL based
- Data is written as JSON
- Every JSON Object represents a node in the DB Tree
- A Node may or may not have child nodes
- Firebase avoids arrays because of a possible key collision on concurrent access of DB Data
- Arrays are replaced by Objects of
key
andvalue
pairs - Keys in the collection are generated by Firebase e.g.
-Jl4Y4ek1Gpb1dLW9R8o
So if the DB has children till the 5th level, and we are accessing any third-level child, we will receive all the data it possesses along with all its children.
Hence, we need to plan the application and the DB structure accordingly. This planning will depend on the following two things:
- Primary Purpose
- Good to Have Features
We are not planning to build any Contact Sharing in this Article; every user has control over their data. This means that there is no intersection among the users.
So as per this plan, we can create users
as the immediate child of DB and contacts
will be the children of users
. This structure will look like the following:
{
"contactdb" :{
"profiles" : {
"1" : {
"email" : "[email protected]",
"location" : {
"city" : "Gurgaon",
"state" : "Haryana",
"zip" : "122001"
},
"name" : "Pankaj Patel"
}
},
"contacts" : {
"1" : {
"-Jl9cQXgm_6YkdC1xgCK" : {
"name" : "Pankaj",
"email" : "[email protected]",
"location" : {
"city" : "Gurgaon",
"state" : "Haryana",
"zip" : "122001"
},
},
},
}
}
}
We need to enable the Email and Password authentication scheme in our Firebase DB for this simple login.
After that, we need to set up the security rules for our DB. It is critical and essential to set up security rules that work with the authentication scheme and DB structure.
As we have decided on the DB structure, we will write the security rules as follows. It will have a 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.
rules
will 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 totrue
ORfalse
.validate
can check what type of data can be written.validate
can also be used to block the writes.validate
can’t check for deletionsauth
is a global variable holding theprovider
anduid
information of userprovider
can bepassword
,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 a 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 the following way:
const data = {
// get the email from the Form
email: $('#registerEmail').val(),
// get the password from the Form
password : $('#registerPassword').val()
};
firebase
.auth()
.createUserWithEmailAndPassword(data.email, data.password)
.then((user) => {
console.log(
"Successfully created user account with uid:",
user.uid
);
})
.catch((error) => {
console.log("Error creating user:", error);
});
In createUserWithEmailAndPassword
, we accomplished the following things sequentially:
- Created a user with supplied email and password
- Logged the user in. This is explained further.
- Saved the extra info in the profiles collection in our DB
Now to log in with the Simple Login scheme in Firebase, authWithPassword
method can be used in DB Reference.
const data = {
email: $('#loginEmail').val(),
password: $('#loginPassword').val()
};
let auth = null;
firebase
.auth()
.signInWithEmailAndPassword(data.email, data.password)
.then((user) => {
console.log("Authenticated successfully with payload:", user);
auth = user;
})
.catch((error) => {
console.log("Login Failed!", error);
});
We have a successful registration and login; we can operate the DB as per our desired data needs.
Here’s the complete code to the sample Contacts Store app created with the Simple Login in Firebase. In the following code, there is some code to make the UI work like an actual application.
To sign out/log out of the user, call the signOut
auth
instance. For example: firebase.auth().signOut()
.
This returns a promise that can handle other tasks like cleaning up user state and data from app and browser local/session storage.
Demo and download links for this Contact Store App:
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">
<link rel="shortcut icon" href="https://time2hack.com/favicon.png" type="image/png">
<link rel="icon" href="https://time2hack.com/favicon.png" type="image/png">
<title>Time to Hack: Simple Login with Firebase Web API</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
<link rel="stylesheet" href="./style.css">
</head>
<body class="auth-false">
<div class="container">
<header class="clearfix">
<h1>Contact Store Application</h1>
<div class="userAuth unauthenticated 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>
</div>
<div class="userAuth authenticated pull-right">
<span class="user-info">
<img src="./user.svg" alt="User" class="rounded-circle">
</span>
<button type="button" class="btn btn-success" id="logout">Logout</button>
</div>
</header>
<hr/>
<main class="authenticated">
<center>
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#addContactModal">Add Contact</button>
</center>
<div id="contacts"></div>
</main>
</div>
<div class="row">
<div class="col-md-6">
<div><iframe src="//time2hack.com/ads.html" frameborder=0 width="100%" class="embed-responsive-item" align="center"></iframe></div>
</div>
<div class="col-md-6">
<div><iframe src="//time2hack.com/ads.html" frameborder=0 width="100%" class="embed-responsive-item" align="center"></iframe></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">
<form id="registerForm" method="POST">
<div class="modal-header">
<h4 class="modal-title" id="registerModalLabel">Register</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
</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="submit" class="btn btn-primary" id="doRegister">Register</button>
</div>
</form>
</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">
<form id="loginForm" method="POST">
<div class="modal-header">
<h4 class="modal-title" id="loginModalLabel">Login</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
</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="submit" class="btn btn-primary" id="doLogin">Login</button>
</div>
</form>
</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" id="contactForm" name="contactForm">
<div class="modal-header">
<h4 class="modal-title" id="addContactModalLabel">Add Contact</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
</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">
<h4 class="modal-title" id="messageModalLabel">Message</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
</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>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" integrity="sha384-smHYKdLADwkXOn1EmN1qk/HfnUcbVRZyYmZ4qpPea6sjB/pTJ0euyQp0Mk8ck+5T" crossorigin="anonymous"></script>
<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:
$(document).ready(() => {
//initialize the firebase app
const 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
const Auth = firebase.auth();
const dbRef = firebase.database();
const contactsRef = dbRef.ref("contacts");
const usersRef = dbRef.ref("users");
let auth = null;
//Register
$("#registerForm").on("submit", (e) => {
e.preventDefault();
$("#registerModal").modal("hide");
$("#messageModalLabel").html(
spanText('<i class="fa fa-cog fa-spin"></i>', ["center", "info"])
);
$("#messageModal").modal("show");
const data = {
email: $("#registerEmail").val(), //get the email from Form
firstName: $("#registerFirstName").val(), // get firstName
lastName: $("#registerLastName").val(), // get lastName
};
const 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((user) =>
user.updateProfile({
displayName: `${data.firstName} ${data.lastName}`,
})
)
.then((user) => {
//now user is needed to be logged in to save data
auth = user;
//now saving the profile data
usersRef
.child(user.uid)
.set(data)
.then(() => console.log("User Information Saved:", user.uid));
$("#messageModalLabel").html(
spanText("Success!", ["center", "success"])
);
$("#messageModal").modal("hide");
})
.catch((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
$("#loginForm").on("submit", (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
const data = {
email: $("#loginEmail").val(),
password: $("#loginPassword").val(),
};
firebase
.auth()
.signInWithEmailAndPassword(data.email, data.password)
.then((authData) => {
auth = authData;
$("#messageModalLabel").html(
spanText("Success!", ["center", "success"])
);
$("#messageModal").modal("hide");
})
.catch((error) => {
console.log("Login Failed!", error);
$("#messageModalLabel").html(
spanText("ERROR: " + error.code, ["danger"])
);
});
}
});
$("#logout").on("click", (e) => {
e.preventDefault();
firebase.auth().signOut();
});
//save contact
$("#contactForm").on("submit", (event) => {
event.preventDefault();
if (!auth) {
//inform user to login
alert("Please login");
return;
}
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!");
}
});
// remove contact
$("#contacts").on("click", "[data-delete-trigger]", (e) => {
// Contact Key
const key = e.target.dataset.key;
// Navigate to the database reference
const contact = contactsRef.child(auth.uid).child(key);
// Remove it from DB
contact.remove();
// Remove the contact card from UI
// needs attribute selector instead of # because of weird characters in id attribute from DB key
$(`[id='${key}']`).remove();
});
firebase.auth().onAuthStateChanged((user) => {
if (user) {
auth = user;
$("body").removeClass("auth-false").addClass("auth-true");
usersRef
.child(user.uid)
.once("value")
.then((data) => {
const info = data.val();
if (user.photoUrl) {
$(".user-info img").show();
$(".user-info img").attr("src", user.photoUrl);
$(".user-info .user-name").hide();
} else if (user.displayName) {
$(".user-info img").hide();
$(".user-info").append(
'<span class="user-name">' + user.displayName + "</span>"
);
} else if (info.firstName) {
$(".user-info img").hide();
$(".user-info").append(
'<span class="user-name">' + info.firstName + "</span>"
);
}
});
contactsRef.child(user.uid).on("child_added", onChildAdd);
} else {
// No user is signed in.
$("body").removeClass("auth-true").addClass("auth-false");
auth && contactsRef.child(auth.uid).off("child_added", onChildAdd);
$("#contacts").html("");
auth = null;
}
});
});
const onChildAdd = (snap) => {
$("#contacts").append(contactHtmlFromObject(snap.key, snap.val()));
};
//prepare contact object's HTML
const contactHtmlFromObject = (key, contact) => `
<div class="card contact" id="${key}">
<div class="card-body">
<button
class="btn btn-default btn-sm delete-button"
data-delete-trigger
data-key="${key}"
title="Delete Contact"
><i class="fa fa-trash"></i></button>
<h5 class="card-title">${contact.name}</h5>
<h6 class="card-subtitle mb-2 text-muted">${contact.email}</h6>
<p class="card-text" title="${contact.location.zip}">
${contact.location.city}, ${contact.location.state}
</p>
</div>
</div>`;
const spanText = (textStr, textClasses) => {
const classNames = textClasses.map((c) => `text-${c}`).join(" ");
return `<span class="${classNames}">${textStr}</span>`;
};
Let me know through comments ? or on Twitter at @heypankaj_ and @time2hack
If you find this article helpful, please share it with others ?
Subscribe to the blog to receive new posts right in your inbox.