Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

User Authentication in a jQuery Mobile and PhoneGap Application

DZone's Guide to

User Authentication in a jQuery Mobile and PhoneGap Application

· Mobile Zone
Free Resource

Get gorgeous, multi-touch charts for your iOS application with just a few lines of code.

This article shows you how to implement user authentication in a jQuery Mobile and PhoneGap application. We will use the Meeting Room Booking app as the sample application for this tutorial. User registration (which we added in the previous chapter of this series) and authentication are the door that users have to go into the application.

The authentication feature has two subsystems that work together. One lives inside the mobile application and captures the user’s credentials, performs a first round of validation, and sends the credentials to the server.

User authentication workflow

The other subsystem resides on the server. It receives the user credentials sent from the mobile application, authenticates them against profile information stored in a database, and returns a session token back to the app so subsequent requests can be mapped to the correct user in the database.

We created the server side of the authentication feature in the User Registration for a Mobile Application tutorial. In this article we are going to create the mobile app side. Let’s get started.

The Sign In Screen

First, we are going to review the Sign In screen that we designed in a previous article of this series. This is where users will type their email address and password for authentication purposes.

The screen is a jQuery Mobile page that uses the following code:

<div data-role="page" id="page-signin">
    <div data-role="header" data-theme="c" data-position="fixed">
        <a href="#page-index" data-icon="home" data-iconpos="notext" data-transition="slide" data-direction="reverse"></a>
        <h1>BookIt</h1>
    </div><!-- /header -->
    <div role="main" class="ui-content">
        <h3>Sign In</h3>
        <div id="ctn-err" class="bi-invisible"></div>
        <label for="txt-email-address">Email Address</label>
        <input type="text" name="txt-email-address" id="txt-email-address" value="">
        <label for="txt-password">Password</label>
        <input type="password" name="txt-password" id="txt-password" value="">
        <fieldset data-role="controlgroup">                
            <label><input type="checkbox" name="chk-keep-signed-in" id="chk-keep-signed-in"> Keep me signed in</label>
        </fieldset>
        <a href="#dlg-invalid-credentials" data-rel="popup" data-transition="pop" data-position-to="window" id="btn-submit" class="ui-btn ui-btn-b ui-corner-all mc-top-margin-1-5">Submit</a>
        <p class="mc-top-margin-1-5"><a href="#begin-password-reset-page">Can't access your account?</a></p>
    </div>
</div>

The page looks like this when the app runs:

Mobile app sign in screen

Besides the email address and password fields, the page has checkbox that lets the users indicate if they want to keep their session active after they close the application. When users check this box, they will be accepted in without authentication next time they launch the app from the same device.

This feature requires that we define a session expiration setting in the app. The setting will represent the number of days the session will be active.

As you can imagine, we will need to create a similar setting on the server side. We need this so the session token created for users when they sign in remains valid on the server for the same period the session is active on the app. The session expiration should be the same on both the app and the server side for this feature to work correctly.

Adding the Main Menu Page

As a second step, we are going to create the screen where we will take users after they log in successfully. We will name it the Main Menu screen.

The screen is a jQuery Mobile page that will add to the index.html file in the cordova-project/www directory:

directories-30

Let’s add the following code to the index.html file, immediately after the “page-signin” div’s end tag:

<div data-role="page" id="page-main-menu">
    <div data-role="header" data-theme="c">
        <h1>Book It</h1>
    </div><!-- /header -->
    <div role="main" class="ui-content">
        <h2 class="mc-text-center">Main Menu</h2>
    </div><!-- /content -->
</div><!-- /page -->

For now we will only place a message in this page indicating that this is the “Main Menu” page. In a future article we will add the main menu elements to the page.

A Note About User Roles

Remember that we planned for the application to also support users with administrative rights. This means that in the future we will need to add user roles logic to the registration and login features. To keep things as simple as possible, we will not touch this area in this particular article.

Now that we have a place to take users after signing in, let’s talk about how we will handle session data.

How to Store Session Data in a PhoneGap Application

The occasionally-connected nature of mobile applications brings along the need to store user information on the application side once you authenticate the user. We will use HTML5 local storage, which is supported by PhoneGap, to persist this information so it’s available each time the user runs the app.

We will use a Class to encapsulate the storage and retrieval of session data in local storage. Let’s add the session.js file to the cordova-project/www/js directory:

directories-31

In the file, define the Session Class with the following code:

var BookIt = BookIt || {};

BookIt.Session = (function () {
    var instance;

    function init() {

        var sessionIdKey = "bookit-session";

        return {
            // Public methods and variables.
            set: function (sessionData) {
                window.localStorage.setItem(sessionIdKey, JSON.stringify(sessionData));
            },

            get: function () {

                var result = null;

                try {
                    result = JSON.parse(window.localStorage.getItem(sessionIdKey));
                } catch(e){}

                return result;
            }
        };
    };

    return {
        getInstance: function () {
            if (!instance) {
                instance = init();
            }
            return instance;
        }
    };
}());

The Session Class is a singleton, which saves us from having instantiate it each time we need to save or retrieve session data. Internally, the Class uses the window.localStorage object to move the data to and from local storage. We call the set method to save data, and the get method to retrieve data.

Confining the session operations to a Class gives us the ability to switch the storage medium – for example, from local storage to file system – without having to modify other modules of the app. What the other modules need to care about is that they can use the Session Class’s set and get methods to save or load session data.

Creating a Controller Class to Perform User Authentication

I mentioned earlier that authenticating a user requires that we perform three steps:

  1. Capture the user’s credentials
  2. Perform a first round of credentials validation (prevent blanks and invalid email addresses)
  3. Send the credentials to the server for authentication

We will implement these operations inside a controller Class that we will name SignInController. Let’s create the signin-controller.js file in the cordova-project/www/js directory:

directories-32

In the file, we will first define the SignInController Class with the following code:

var BookIt = BookIt || {};

BookIt.SignInController = function () {

    this.$signInPage = null;
    this.$btnSubmit = null;
    this.$txtEmailAddress = null;
    this.$txtPassword = null;
    this.$chkKeepSignedIn = null;
    this.$ctnErr = null;
    this.mainMenuPageId = null;
};

The $signInPage variable will hold a reference to the jQuery Mobile page that hosts the Sign In screen. The $btnSubmit, $ctnError, $txtEmailAddress, $txtPassword and $chkKeepSignedIn will hold references to the form elements in the screen and the div element where we will display error messages. The mainMenuPageId variable will hold the id of the jQuery Mobile page that hosts the Main Menu screen.

Input Capture

Next, we are going to create a method where we will acquire the references we declared in the constructor of the Class. Let’s add the init method like so:

BookIt.SignInController.prototype.init = function () {
    this.$signInPage = $("#page-signin");
    this.mainMenuPageId = "#page-main-menu";
    this.$btnSubmit = $("#btn-submit", this.$signInPage);
    this.$ctnErr = $("#ctn-err", this.$signInPage);
    this.$txtEmailAddress = $("#txt-email-address", this.$signInPage);
    this.$txtPassword = $("#txt-password", this.$signInPage);
    this.$chkKeepSignedIn = $("#chk-keep-signed-in", this.$signInPage);
};

In the init method, we use jQuery to acquire the element references that we need. Nothing earth-shattering going on here.

Email Validation

I mentioned that we are going to create a layer of credentials validation inside the app before sending the credentials to the server. One of the pieces of this layer is the validation of the email address entered by the user. Let’s define the emailAddressIsValid method:

BookIt.SignInController.prototype.emailAddressIsValid = function (email) {
    var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    return re.test(email);
};

his helper method performs a quick regular expression check that returns true if the supplied email address is valid.

Resetting the SignIn Form

We also need a helper method to clear the email address, password and “keep me signed in” form elements when the Sign In screen is activated. Clearing these elements prevents us from accidentally showing any values entered previously.

Let’s create the resetSignInForm method in the Class:

BookIt.SignInController.prototype.resetSignInForm = function () {

    var invisibleStyle = "bi-invisible",
        invalidInputStyle = "bi-invalid-input";

    this.$ctnErr.html("");
    this.$ctnErr.removeClass().addClass(invisibleStyle);
    this.$txtEmailAddress.removeClass(invalidInputStyle);
    this.$txtPassword.removeClass(invalidInputStyle);
    this.$txtEmailAddress.val("");
    this.$txtPassword.val("");
    this.$chkKeepSignedIn.prop("checked", false);
};

In this method we clear the html in the div that we use to display error messages, and clear the values of the email and password text fields. We also uncheck the “keep me signed in” checkbox, and remove the styles that we use to flag invalid fields.

The invisibleStyle and invalidInputStyle variables hold the names of the CSS classes that we use to hide or flag fields as invalid. We created these classes in a previous chapter of this series, and you can find them in the cordova-project/www/css/app.css file. Here they are so you don’t have to look up the file right now:

.bi-invalid-input {
background-color:#fffacd!important;
}

.bi-invisible {
display:none;
}

Submitting User Credentials to the Server from a PhoneGap Application

The third step of the authentication sequence in our Cordova app is to send the user credentials to the server, and wait for a response that will tell us if we can let the user in.

We will perform this step inside a template method that we will call onSignInCommand. We will invoke this method when the user pushes the Sign In button in the Sign In Screen.

Let’s add the following code to the controller Class:

BookIt.SignInController.prototype.onSignInCommand = function () {

    var me = this,
        emailAddress = me.$txtEmailAddress.val().trim(),
        password = me.$txtPassword.val().trim(),
        invalidInput = false,
        invisibleStyle = "bi-invisible",
        invalidInputStyle = "bi-invalid-input";

    // Reset styles.
    me.$ctnErr.removeClass().addClass(invisibleStyle);
    me.$txtEmailAddress.removeClass(invalidInputStyle);
    me.$txtPassword.removeClass(invalidInputStyle);

    // Flag each invalid field.
    if (emailAddress.length === 0) {
        me.$txtEmailAddress.addClass(invalidInputStyle);
        invalidInput = true;
    }
    if (password.length === 0) {
        me.$txtPassword.addClass(invalidInputStyle);
        invalidInput = true;
    }

    // Make sure that all the required fields have values.
    if (invalidInput) {
        me.$ctnErr.html("<p>Please enter all the required fields.</p>");
        me.$ctnErr.addClass("bi-ctn-err").slideDown();
        return;
    }

    if (!me.emailAddressIsValid(emailAddress)) {
        me.$ctnErr.html("<p>Please enter a valid email address.</p>");
        me.$ctnErr.addClass("bi-ctn-err").slideDown();
        me.$txtEmailAddress.addClass(invalidInputStyle);
        return;
    }

    $.mobile.loading("show");

    $.ajax({
        type: 'POST',
        url: BookIt.Settings.signInUrl,
        data: "email=" + emailAddress + "&password=" + password,
        success: function (resp) {

            $.mobile.loading("hide");

            if (resp.success === true) {
                // Create session. 
                var today = new Date();
                var expirationDate = new Date();
                expirationDate.setTime(today.getTime() + BookIt.Settings.sessionTimeoutInMSec);

                BookIt.Session.getInstance().set({
                    userProfileModel: resp.extras.userProfileModel,
                    sessionId: resp.extras.sessionId,
                    expirationDate: expirationDate,
                    keepSignedIn:me.$chkKeepSignedIn.is(":checked")
                });
                // Go to main menu.
                $.mobile.navigate(me.mainMenuPageId);
                return;
            } else {
                if (resp.extras.msg) {
                    switch (resp.extras.msg) {
                        case BookIt.ApiMessages.DB_ERROR:
                        // TODO: Use a friendlier error message below.
                            me.$ctnErr.html("<p>Oops! BookIt had a problem and could not log you on.  Please try again in a few minutes.</p>");
                            me.$ctnErr.addClass("bi-ctn-err").slideDown();
                            break;
                        case BookIt.ApiMessages.INVALID_PWD:
                        case BookIt.ApiMessages.EMAIL_NOT_FOUND:
                            me.$ctnErr.html("<p>You entered a wrong username or password.  Please try again.</p>");
                            me.$ctnErr.addClass("bi-ctn-err").slideDown();
                            me.$txtEmailAddress
.addClass(invalidInputStyle);
                            break;
                    }
                }
            }
        },
        error: function (e) {
            $.mobile.loading("hide");
            console.log(e.message);
            // TODO: Use a friendlier error message below.
            me.$ctnErr.html("<p>Oops! BookIt had a problem and could not log you on.  Please try again in a few minutes.</p>");
            me.$ctnErr.addClass("bi-ctn-err").slideDown();
        }
    });
};

Inside the method we first reset the styles of any fields that were marked as invalid previously. We do this by hiding the div element where we show error messages, and removing the “invalid input” CSS classes from the email address and password fields:

// Reset styles.
me.$ctnErr.removeClass().addClass(invisibleStyle);
me.$txtEmailAddress.removeClass(invalidInputStyle);
me.$txtPassword.removeClass(invalidInputStyle);

Next, we check that the user typed some text in the email and password fields. If either field is empty, we show an error message in the “errors” div:

// Flag each invalid field.
if (emailAddress.length === 0) {
    me.$txtEmailAddress.addClass(invalidInputStyle);
    invalidInput = true;
}
if (password.length === 0) {
    me.$txtPassword.addClass(invalidInputStyle);
    invalidInput = true;
}

// Make sure that all the required fields have values.
if (invalidInput) {
    me.$ctnErr.html("<p>Please enter all the required fields.</p>");
    me.$ctnErr.addClass("bi-ctn-err").slideDown();
    return;
}

As a third step, we invoke the emailAddressIsValid method created earlier to check that the text entered in the email address field is formatted as an email address:

if (!me.emailAddressIsValid(emailAddress)) {
    me.$ctnErr.html("<p>Please enter a valid email address.</p>");
    me.$ctnErr.addClass("bi-ctn-err").slideDown();
    me.$txtEmailAddress.addClass(invalidInputStyle);
    return;
}

Finally, we activate the jQuery Mobile load indicator to signal that we are starting a long-running operation, and submit the credentials to the server through an AJAX request.

$.mobile.loading("show");

$.ajax({
    type: 'POST',
    url: BookIt.Settings.signInUrl,
    data: "email=" + emailAddress + "&password=" + password,
    success: function (resp) {

        $.mobile.loading("hide");

        // Success handler
    },
    error: function (e) {
        $.mobile.loading("hide");

        // Error handler
    }
});

This is a POST request. We obtain the URL for it from the signInUrl property of the Settings Class. The request’s data property contains the email address and password that we are sending to the server.

Here the interesting stuff happens inside the success function. In it, we hide the jQuery Mobile loading indicator and check the success property of the resp argument, which is sent by the server endpoint:

success: function (resp) {

    $.mobile.loading("hide");

    if (resp.success === true) {

    } else {

    }
}

Creating a Session Object in a Cordova/PhoneGap Application

The resp.success property is true when the authentication succeeds. In such a case, we need to create a session object for the current user:

// Create session. 
var today = new Date();
var expirationDate = new Date();
expirationDate.setTime(today.getTime() + BookIt.Settings.sessionTimeoutInMSec);

BookIt.Session.getInstance().set({
    userProfileModel: resp.extras.userProfileModel,
    sessionId: resp.extras.sessionId,
    expirationDate: expirationDate,
    keepSignedIn:me.$chkKeepSignedIn.is(":checked")
});
// Go to main menu.
$.mobile.navigate(me.mainMenuPageId);
return;

Notice how we use the Settings.sessionTimeoutInMSec (milliseconds) to create the session expiration date. Also, how we use the Session singleton that we created earlier to save the user profile and session id sent from the server.

We also save the session’s expiration date, and whether the user wants the app to keep her or him signed in the next time they run the app. (Later in this article we will create the code that bypasses the Sign In Screen when a user launches the application.)

After saving the session, we activate the Main Menu screen using jQuery Mobile’s navigate method.

Handling Authentication Failures

The success property returned by the server is false when the authentication fails. In such a case, we need to check the value of the response’s extras.msg property to determine the reason for the failure:

if (resp.extras.msg) {
    switch (resp.extras.msg) {
        case BookIt.ApiMessages.DB_ERROR:
        // TODO: Use a friendlier error message below.
            me.$ctnErr.html("<p>Oops! BookIt had a problem and could not log you on.  Please try again in a few minutes.</p>");
            me.$ctnErr.addClass("bi-ctn-err").slideDown();
            break;
        case BookIt.ApiMessages.INVALID_PWD:
        case BookIt.ApiMessages.EMAIL_NOT_FOUND:
            me.$ctnErr.html("<p>You entered a wrong username or password.  Please try again.</p>");
            me.$ctnErr.addClass("bi-ctn-err").slideDown();
            me.$txtEmailAddress.addClass(invalidInputStyle);
            break;
    }
}

If the server tells us that there was a database error, we alert the user that she or he needs to try again later. We also notify the user when the server tells us that either the email address or password supplied is invalid.

While I am showing you that you can get really detailed with the information that you send from the server, in a production app that’s open to the general public I wouldn’t bubble a database error code all the way to the mobile app. I wouldn’t use separate codes to indicate invalid password and invalid email address either.

Be mindful not to disclose sensitive information when handling response codes from the server. People with bad intentions can use these codes to attack the server endpoint.

Handling Ajax Errors

The remaining code in the Ajax request in the error handler. In it, we just show a generic error to the user:

error: function (e) {
    $.mobile.loading("hide");
    console.log(e.message);
    // TODO: Use a friendlier error message below.
    me.$ctnErr.html("<p>Oops! BookIt had a problem and could not log you on.  Please try again in a few minutes.</p>");
    me.$ctnErr.addClass("bi-ctn-err").slideDown();
}

Including the Session and Controller Classes in the Cordova Project

Let’s go ahead and add references to the session.js and sigin-controller.js files in the cordova-project/www/index.html file. We will add them immediately before the reference to the index.js file:

<script type="text/javascript" src="cordova.js"></script>
<script src="lib/fastclick/fastclick.min.js"></script>
<script src="lib/jquery/2.1.1/jquery-2.1.1.min.js"></script>
<script src="js/api-messages.js"></script>
<script src="js/settings.js"></script>
<script src="js/signup-controller.js"></script>
<script src="js/session.js"></script>
<script src="js/signin-controller.js"></script>
<script type="text/javascript" src="js/index.js"></script>
<script src="lib/jqm/1.4.5/jquery.mobile-1.4.5.min.js"></script>

Instantiating the Controller

Now we can move on to actually using the SignInController Class to perform the authentication. Let’s jump to the index.js file and instantiate the controller. We can do it right after the line that instantiates the SignUpController, which we created in an earlier chapter of this series:

app.signUpController = new BookIt.SignUpController();
app.signInController = new BookIt.SignInController();

The first action that we will take with the signInController instance is to reset the email address and password fields every time the Sign In screen is activated. We will use jQuery Mobile’s pagecontainerbeforeshow event to detect when the Sign In screen is about to be shown, and reset the fields:

$(document).on("pagecontainerbeforeshow", function (event, ui) {
    if (typeof ui.toPage == "object") {
        switch (ui.toPage.attr("id")) {
            case "page-signup":
                // Reset the signup form.
                app.signUpController.resetSignUpForm();
                break;
            case "page-signin":
                // Reset signin form.
                app.signInController.resetSignInForm();
                break;
        }
    }
});

From the code above you will remember that we did the same for the Sign Up screen.

Wiring the Sign In Button to the Controller

We also need to wire the Sign In button on the Sign In sccreen to the controller instance. We will use the page’s beforecreate event to wire a tap handler for the button.

This event triggers before the jQuery Mobile page has been created. We want the tap handler to be already wired when the page is created and shown to the user.

$(document).delegate("#page-signin", "pagebeforecreate", function () {

    app.signInController.init();

    app.signInController.$btnSubmit.off("tap").on("tap", function () {
        app.signInController.onSignInCommand();
    });
});

Perfect. Now when a user taps the button, the app will invoke the controllers onSignInCommand method, which will perform the validation of the fields and submit the credentials to the server.

Bypassing the Index Screen Upon Launch if a Session is Still Active

This is a feature that many users will appreciate. The majority of people don’t want to have to type their email address and password every time they launch the application.

If someone went through the Sign In screen and checked off the box indicating that they want to be kept signed in, we saved this selection to local storage. Now we have to add the code to read this flag upon application launch and take the user straight into the app without showing the Index, Sign In or Sign Up screens.

The trick here is to know when the Index page is about to be activated, and both prevent the activation and trigger the navigation to the Main Menu screen, if the “keep me signed in” flag is set. Let’s add the following code to the index.js file:

$(document).on("pagecontainerbeforechange", function (event, ui) {

    if (typeof ui.toPage !== "object") return;

    switch (ui.toPage.attr("id")) {
        case "page-index":
            if (!ui.prevPage) {
                // Check session.keepSignedIn and redirect to main menu.
                var session = BookIt.Session.getInstance().get(),
                    today = new Date();
                if (session && session.keepSignedIn && new Date(session.expirationDate).getTime() > today.getTime()) {
                    ui.toPage = $("#page-main-menu");                }
            }
    }
});

jQuery Mobile triggers the pagecontainerbeforechange event during the page change cycle prior to any page loading or transition. We can use the event to detect when the user interface is navigating to the Index screen for the first time. We know this is the case when:

  1. the id of the ui.toPage object is the id of the Index screen and
  2. the ui.prePage object is null, meaning that we are coming to the Index screen from another screen

When we are opening the Index screen for the first time after application launch, we check if there’s an existing non-expired session for which the keepMeSignedIn flag is set. If the session exists, we navigate to the Main Menu screen by replacing the ui.toPage object with the jQuery Mobile page that contains the Main Menu screen.

Summary and Next Steps

In this tutorial we created a user login feature for a Cordova application. The feature has in-application and server-side modules that work together to capture the user’s credentials, validate them and authenticate the user by comparing their email address and a hash of their password against data stored in a database.

In the next chapter of this series we will start implementing the room booking features of the app. Make sure to sign up for my newsletter so you can be among the first to know when my next tutorial is available.

Download Source Code

 
Download the source code for this tutorial here: PhoneGap application user signup tutorial on GitHub

.Net developers: use Highcharts, the industry's leading interactive charting library, without writing a single line of JavaScript.

Topics:
java ,mobile ,phonegap ,jquery mobile

Published at DZone with permission of Jorge Ramon, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}