Offline Authentication
improve this page | report issueOverview
This tutorial explains a sample implementation of offline user authentication.
The goal of offline authentication is to make the login/logout end-user experience consistent whether the end user works online or offline.
Prerequisites
It is assumed that the developer is familiar with IBM MobileFirst Platform Foundation application development, adapter-based authentication, and JSONStore.
The associated sample project demonstrates an end-to-end scenario. However, there are many ways to achieve the same results.
This tutorial covers the following topics:
Offline authentication concept
By using offline authentication, an end user is able to authenticate even if no Internet connection is available.
Online login is necessary when logging-in is attempted for the first time, so that MobileFirst Server validates the user’s credentials. After successful validation, a JSONStore object is created for the logged-in user. You implement offline authentication by opening the JSONStore object for the supplied username and password. This is further explained below.
The sample application for this tutorial has the following structure:
- An unsecured area, which is accessible without authentication
- A secured area, which is accessible only after authentication, whether online or offline
- The same login form for both online and offline authentication, so that the end-user experience is the same in either case
- Support for logging out
Going through the online and offline flows from a high-level point of view:
Step 1:
Before either online or offline authentication occurs, the availability of an Internet connection is checked first. Depending on the result, the appropriate authentication flow is used.
Online authentication
Steps 2 and 3
The online authentication flow is implemented as explained in the adapter-based authentication in hybrid applications tutorial.
Steps 4 and 5
After successful login, a JSONStore is created by using the end-user’s username and encrypted by using the user's password.
Step 6
The secured area is displayed.
Offline authentication
Steps 2 and 3
The authentication process tries to open the previously created JSONStore by using the end-user’s password that was passed as input.
Step 4
If the attempt is successful, the secured area is displayed.
Implementing the authentication flows
Note: Some of the implementation code relates to the application UI.
You can find the rest of the application code (HTML, CSS, additional JavaScript) in the sample application.
The username
, password
and offlineLoggedIn
global variables help determine the user’s login state, whether connected or not connected to the Internet.
var username = null, password = null;
var offlineLoggedIn = false;
Prerequisite: When an end user tries to log in for the very first time, an Internet connection must be available so that the user credentials can be verified against the server back end.
Therefore, before any authentication is performed, the Internet connection is checked so that the appropriate authentication flow is executed.
function goToSecuredArea() {
WL.Device.getNetworkInfo(
function (networkInfo) {
if (networkInfo.isNetworkConnected == "true") {
onlineAuthentication();
} else {
offlineAuthentication();
}
}
);
}
Implementation notes
Single-Step authentication
- The online authentication flow uses the same code as the one provided in the single-step adapter authentication sample application.
- To simulate a back end of multiple users, a login is considered valid when
username == password
, no matter whatusername
is.The
submitAuthentication
function in the adapter*-impl.js
file is therefore modified to:
function submitAuthentication (username, password){ if (username == password) {
JSONStore
Because the authentication process simulates multiple users, you must use multiple JSONStores, too, in order to accommodate the multiple users.
Thus, in the WL.JSONStore.init
file, the username
string is passed as a parameter for a new JSONStore to be opened for each authenticated user. For this, declare a collection to define the structure of the JSONStore:
var collectionName = "userCredentials";
var collections = {
userCredentials : {
searchFields : {
collectionNotEmpty: "string"
}
}
};
Online authentication
Below is the complete implementation, followed by a breakdown and explanation, code block by code block.
The sample application contains additional comments.
function onlineAuthentication() {
if (WL.Client.isUserAuthenticated("myCustomRealm")) {
if (username == null) {
WL.Client.logout("myCustomRealm", {
onSuccess: function() {
onlineAuthentication();
},
onFailure: function() {
WL.SimpleDialog.show("Logout", "Unable to logout at this time. Try again later.", [{
text: "OK",
handler: function() {}
}]);
}
});
} else {
displaySecuredArea();
}
} else if (username) {
var invocationData = {
adapter: "authenticationAdapter",
procedure: "submitAuthentication",
parameters: [$("#username").val(), $("#password").val()]
};
myCustomRealmChallengeHandler.submitAdapterAuthentication(
invocationData, {
onSuccess: onlineAuthenticationSuccess,
onFailure: onlineAuthenticationFailure
});
} else {
WL.Client.login("myCustomRealm", {
onSuccess: onlineAuthenticationSuccess,
onFailure: onlineAuthenticationFailure
});
}
}
- Check whether the user is already authenticated.
- If the check returns
true
, display the secured area. - If the check returns
false
, display the login form and start authentication via the challenge handler.
function onlineAuthentication() { if (WL.Client.isUserAuthenticated("myCustomRealm")) { if (username == null) { //... }); } else { displaySecuredArea(); } } else if (username) { //... } else { WL.Client.login("myCustomRealm", { onSuccess: onlineAuthenticationSuccess, onFailure: onlineAuthenticationFailure }); } }
- If the check returns
- If the Internet connection is established and the user is authenticated on the back end but the
username
variable is empty, assume that a logout happened offline.
Log out the user from the realm before trying to log in again online.if (WL.Client.isUserAuthenticated("myCustomRealm")) { if (username == null) { WL.Client.logout("myCustomRealm", { onSuccess: function() { onlineAuthentication(); }, onFailure: function() { WL.SimpleDialog.show("Logout", "Unable to logout at this time. Try again later.", [{ text: "OK", handler: function() {} }]); } }); } else { displaySecuredArea(); } }
- If logging-in occurs offline and an Internet connection is then available, log in to the realm without showing the login form.
function onlineAuthentication() { if (WL.Client.isUserAuthenticated("myCustomRealm")) { //... } else if (username) { var invocationData = { adapter: "authenticationAdapter", procedure: "submitAuthentication", parameters: [$("#username").val(), $("#password").val()] }; myCustomRealmChallengeHandler.submitAdapterAuthentication( invocationData, { onSuccess: onlineAuthenticationSuccess, onFailure: onlineAuthenticationFailure }); } else { //... } }
- If online authentication fails, for example if MobileFirst Server is unavailable, offline authentication is attempted instead.
function onlineAuthenticationFailure() {
offlineAuthentication();
}
Online authentication success
Upon successful online authentication, the secured area can be displayed.
The username and password are saved for future use.
A JSONStore for the user is initialized from the user’s username
string and encrypted by using the user’s password.
Before populating the collection, it is best to check whether it is already populated from a previous login attempt. The check returns the contents of the collection.
- If the collection is empty, populate it.
- If the collection is not empty, do not populate it again.
Finally, the username and password variables are emptied, the JSONStore is closed and the secured area is displayed.
function onlineAuthenticationSuccess() {
username = $("#username").val();
password = $("#password").val();
WL.JSONStore.init(collections, {password:password, username:username, localKeyGen:true})
.then(function() {
return WL.JSONStore.get(collectionName).findAll();
})
.then(function(findAllResult) {
if (findAllResult.length == 0) {
// The JSONStore collection is empty, populate it.
var data = [{isEmpty:"false"}];
return WL.JSONStore.get(collectionName).add(data);
}
})
.then(function() {
password = null;
username = null;
WL.JSONStore.closeAll();
displaySecuredArea();
})
}
Offline authentication
In online authentication, if username != password
, the authentication process stops. In contrast, during offline authentication in JSONStore, you must deal with a couple of additional scenarios:
- Empty username and password fields
- First-time log-in when offline
- Already authenticated users
Empty username and password fields
Empty username and password fields destroy your JSONStore. As a result, users can no longer log in offline until the next online authentication, where the JSONStore is created again if it does not exist.
First-time log-in
First-time log-in cannot happen because there is no JSONStore yet.
function offlineAuthentication() {
$("#username").val('');
$("#password").val('');
$("#authInfo").empty();
// Don't show the login form if already offline-logged-in via JSONStore, or previously logged-in online (but have yet logged-out).
if (offlineLoggedIn === true || username) {
offlineLoggedIn = true;
displaySecuredAreaOffline();
} else {
// Prepare the login form.
$("#unsecuredDiv").hide();
$("#authDiv").show();
$("#offlineLoginButton").show();
$("#onlineLoginButton").hide();
$("#offlineLoginButton").unbind("click");
$("#offlineLoginButton").bind("click", function() {
// Don't allow empty username/password field.
if (($("#username").val() == '') || ($("#password").val() == '')) {
$("#authInfo").html("Invalid credentials. Please try again.");
} else {
WL.JSONStore.init(collections, {
password: $("#password").val(),
username: $("#username").val(),
localKeyGen: true
})
.then(function() {
/*
* Need to handle the case where logging in when offline for the first time, in which case logging in cannot be done as there wouldn't be a
* JSONStore available yet.
*/
WL.JSONStore.get(collectionName).count()
.then(function(countResult) {
if (countResult == 0) {
WL.JSONStore.destroy($("#username").val());
$("#authInfo").html("First time login must be done when Internet connection is available.");
$("#username").val("");
$("#password").val("");
} else {
// Preserve the username and password, set the offline-logged-in flag and dislay the secured area.
offlineLoggedIn = true;
username = $("#username").val();
password = $("#password").val();
displaySecuredAreaOffline();
}
})
})
.fail(function() {
$("#authInfo").html("Invalid credentials. Please try again.");
$("#username").val("");
$("#password").val("");
})
}
});
}
}<br />
Already authenticated users
- This function first checks whether the user is already authenticated.
if (offlineLoggedIn === true || username) { offlineLoggedIn = true; displaySecuredAreaOffline(); }
If the user is not authenticated, offline authentication is attempted. - Check the validity of the user credentials.
- If they are not valid, display an error message.
- If they are valid, try to open and decrypt the user’s JSONStore by using the user’s password.
$("#offlineLoginButton").bind("click", function() { // Don't allow empty username/password field. if (($("#username").val() == '') || ($("#password").val() == '')) { $("#authInfo").html("Invalid credentials. Please try again."); } else { WL.JSONStore.init(collections, { password: $("#password").val(), username: $("#username").val(), localKeyGen: true })
- Check the contents of the store.
- If the store is empty, this user did not previously log in: no stored data in the collection.
Destroy the store for this username and provide an appropriate error message..then(function() { /* * Need to handle the case where logging in when offline for the first time, in which case logging in cannot be done as there wouldn't be a * JSONStore available yet. */ WL.JSONStore.get(collectionName).count() .then(function(countResult) { if (countResult == 0) { WL.JSONStore.destroy($("#username").val()); $("#authInfo").html("First time login must be done when Internet connection is available."); $("#username").val(''); $("#password").val('');
- If the JSON store is NOT empty, set the
offlineLoggedIn
flag totrue
and display the secured area.} else { // Preserve the username and password, set the offline-logged-in flag and dislay the secured area. offlineLoggedIn = true; username = $("#username").val(); password = $("#password").val(); displaySecuredAreaOffline(); }
- If the store is empty, this user did not previously log in: no stored data in the collection.
Logout
function logout() {
password = null;
username = null;
WL.JSONStore.closeAll();
offlineLoggedIn = false;
WL.Device.getNetworkInfo(
function(networkInfo) {
if (networkInfo.isNetworkConnected == "true") {
WL.Client.logout("myCustomRealm", {onSuccess: displayUnsecuredArea, onFailure: displayUnsecuredArea});
}
}
);
displayUnsecuredArea();
}
- Upon logout, mark the user as logged out:
- Empty the username and password variables.
- Close the JSONStore.
- Set the
offlineLoggedIn
flag tofalse
.
password = null; username = null; WL.JSONStore.closeAll(); offlineLoggedIn = false;
- Check the Internet connection before logging out.
- If available, log out the user from the realm and display the unsecured area.
WL.Device.getNetworkInfo( function(networkInfo) { if (networkInfo.isNetworkConnected == "true") { WL.Client.logout("myCustomRealm", {onSuccess: displayUnsecuredArea, onFailure: displayUnsecuredArea}); } } ); displayUnsecuredArea();
- If available, log out the user from the realm and display the unsecured area.
- If online logout fails (for example because MobileFirst Server is not available), logging out takes place anyway. See online authentication in case the
username
variable is empty.
The end result
Note: The sample code that is written in this tutorial is only a suggestion. You can use other implementation methods.
To use the sample:
- Import the MobileFirst project into MobileFirst Studio.
- Deploy the adapter and application.
- Start the application on a device.
- Start online and log in by using, for example,
mobile
/mobile
as the username and password. The secured area is then displayed. - Log out.
- In the device settings, change to airplane mode and log in again. The secured area is then displayed, in "offline access".
Sample application
Click to download the sample application.
Inclusive terminology note: The Mobile First Platform team is making changes to support the IBM® initiative to replace racially biased and other discriminatory language in our code and content with more inclusive language. While IBM values the use of inclusive language, terms that are outside of IBM's direct influence are sometimes required for the sake of maintaining user understanding. As other industry leaders join IBM in embracing the use of inclusive language, IBM will continue to update the documentation to reflect those changes.