Building a secure app

Now that we have a better understanding of client-side security and its drawbacks, let's put it into practice by developing an app with the following features:

  • There is a public home screen that can be seen by everybody who uses the app
  • There is a private part that shows some personal information about a user, which is only accessible to authenticated users
  • There is logic for the authentication of users through a simple log-in form
  • There is logic for the authorization and authentication of users to access the otherwise private parts of the application

Starting off

Let's start with the configuration of our basic project structure. If you have read the book until this point, this should be second nature to you by now! Go to a desired project directory, and from there, just run the following from your terminal or command line:

ionic start secureApp

This will create a basic, blank Ionic app. Let's add some basic structure to it. The first thing that we want to do is add two basic navigation states—home and public. Navigate to your app's www/js folder and make sure that app.js has the following:

angular.module('secureApp', [])
.run(function ($ionicPlatform) {
  $ionicPlatform.ready(function () {
    // Hide the accessory bar by default (remove this to show
    // the accessory bar above the keyboard for form inputs)
    if (window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
    }
    if (window.StatusBar) {
      // org.apache.cordova.statusbar required
      StatusBar.styleDefault();
    }
  });
})
.config(function ($stateProvider, $urlRouterProvider) {
  $stateProvider
    .state('app', {
       url: "/app",
       abstract: true,
       templateUrl: "templates/menu.html"
    })
    .state('app.home', {
      url: "/home",
      views: {
        'menuContent': {
          templateUrl: "templates/home.html"
        }
      }
    })
    .state('app.private', {
      url: "/private",
      views: {
        'menuContent': {
          templateUrl: "templates/private.html"
        }
      }
    });
    // if none of the above states are matched, use this as the fallback
    $urlRouterProvider.otherwise('/app/home');
  });

This will set up the essential navigation states for the app, which fortunately are very few at this point! However, we still need to add the necessary templates. Inside the www directory, create a templates directory and add the following three files to the path www/templates/menu.html:

<ion-side-menus enable-menu-with-back-views="false">
  <ion-side-menu-content>
    <ion-nav-bar class="bar-stable">
      <ion-nav-back-button></ion-nav-back-button>
        <ion-nav-buttons side="left">
          <button class="button button-icon button-clear ion-navicon"
          menu-toggle="left">
          </button>
        </ion-nav-buttons>
      </ion-nav-bar>
      <ion-nav-view name="menuContent"></ion-nav-view>
      </ion-side-menu-content>
      <ion-side-menu side="left">
        <ion-header-bar class="bar-stable">
          <h1 class="title">Left</h1>
        </ion-header-bar>
        <ion-content>
          <ion-list>
            <ion-item menu-close
            href="#/app/home">
              Home
            </ion-item>
            <ion-item menu-close
            href="#/app/private">
              Private
            </ion-item>
          </ion-list>
        </ion-content>
      </ion-side-menu>
    </ion-side-menus>

The following code snippet represents the home.html templates at the path www/templates/home.html:

<ion-view view-title="Search">
  <ion-content class="has-header">
    <h1>A secure app!</h1>
    <div class="card">
      <div class="item item-text-wrap">
        This app contains extremely secretive confidential
        mustneverbeseen-ish information that will cause a
        disaster if it leaks out. It will also kill all
        dolphins. Please save the dolphins.
      </div>
    </div>
  </ion-content>
</ion-view>

The following code snippet represents the private.html templates at the path www/templates/private.html:

<ion-view view-title="Search">
  <ion-content class="has-header">
    <h1>Secret content!</h1>
    <div class="card">
      <div class="item item-text-wrap">
        You are authorized to see the grand secrets
        of the Universe!
      </div>
    </div>
  </ion-content>
</ion-view>

That's all that we need for the basic setup. You can verify it by running the following in a terminal or command line in the root folder of your directory:

Ionic serve -l

You will see the following:

Starting off

A dire warning indeed! Let's see if we can get around it. If you click on the app icon at the top left of the app screen (either for Android or iOS), you can bring out the navigation drawer that we created in the www/templates/menu.html file:

Starting off

If you select the Private link from the list, you would expect the app to stop us from accessing information that could potentially put an end to Flipper once and for all, but alas:

Starting off

Not good! To remedy this, we will need to find a way to block the user from accessing certain content unless they are authenticated and that, even if they hack their way into accessing the content, there is no useful data for them to find anywhere.

A basic authentication service

The first step in adding basic security to our app is to create an authentication service, which can be used in order to carry out authentication requests. We want this service to provide the following functionalities:

  • It should be able to log a user in. This function should take a username and password and, if they match, return an authentication token that the user can use in order to verify their identity to the system.
  • It should be able to check whether a user is currently authenticated in the app. This will be necessary whenever we wish to check whether a user should have access to the system or not.

Let's go ahead and build such a service. Add the services.js file in the www/js folder and insert the following content in it:

angular.module('secureApp.services', [])
.factory('AuthFactory', function ($scope, $timeout) {
  var currentUser = null;
  var login = function (username, password) {
    return null;
  };
  var isAuthenticated = function () {
    return false;
  };
  var getCurrent = function () {
    return isAuthenticated() ? currentUser : null;
  };
  return {
    login: login,
    isAuthenticated: isAuthenticated,
    getCurrent: getCurrent
  }
});

This gives us a skeleton to work with. Let's start adding some meat to it incrementally.

The login function

The purpose of the login function is simply to take a username and password and check them against an existing list of such pairs. To get it working, we will first need to add some mock data to our service (in real life, you will of course pull the data from a remote server).

Go ahead and make sure that the LoginFactory contains the following:

var validUsers = [
{
  firstName: 'Johanna',
  lastName: 'Doe',
  username: 'johnny',
  password: 'suchsecret'
},
{
  firstName: 'Jane',
  lastName: 'Doe',
  username: 'zo1337',
  password: 'muchhide'
},
{
  firstName: 'Mary',
  lastName: 'Doe',
  username: 'bl00dy',
  password: 'wow'
}
];

Now, we simply need to add the following to the body of the login function:

var login = function (username, password) {
  var deferred = $q.defer();
  // We use timeout in order to simulate a roundtrip to a server,
  // which will be present in any realistic authentication scenario.
  $timeout(function () {
    // Clear any existing, cached user data before logging in
    currentUser = null;
    // See if we can find a matching username-password match
    validUsers.forEach(function (user) {
      if (user.username === username && user.password === password) {
        // If we have a match, cache it as the current user
        currentUser = user;
        deferred.resolve();
      }
    });
    // If no match could be found, reject the promise
    if (!currentUser) {
      deferred.reject();
    }
  }, 1000);
  // Return the promise to the caller
  return deferred.promise;
};

What we do here in terms of authentication is really quite simple. We only match usernames and passwords against a pre-defined array. If a match is found, we cache the matched user and add it to the factories context. It will now be accessible via the getCurrent() function.

The isAuthenticated function

The purpose of this function is to allow the system to check whether the current user is presently logged in or not. We can simply implement it in terms of whether there is a cached user from a successful login event available:

var isAuthenticated = function () {
  return currentUser ? true : false;
};

The getCurrent function

This function is simple, and it simply returns the current cached user:

var getCurrent = function () {
  return isAuthenticated() ? currentUser : null;
};

Implementing route authentication

Now that we have a working authentication service, let's use it in order to safeguard the world's dolphins and seal off the private part of our app. To do so, first make sure that the index.html file correctly imports the new service, as follows:

<script src="js/services.js"></script>

Next, modify the app.js file to import that file as well:

angular.module('secureApp',
[
  'ionic',
  'secureApp.services',
])

Now, in the app.js file, modify the routing config for the private part of the app so that it looks like the following code:

.state('app.private', {
  url: "/private",
  views: {
    'menuContent': {
    templateUrl: "templates/private.html",
    resolve: {
      isAuthenticated: function ($q, AuthFactory) {
        if (AuthFactory.isAuthenticated()) {
          return $q.when();
        } else {
          $timeout(function () {
          $state.go('app.home')
        },0);
        return $q.reject()
      }
    }
   }
});

What is going on here? To answer this, consider what we want to achieve. If the user is not authenticated, we want to send them back to the home screen until they have logged in. In order to do so, we perform the following steps:

  1. We add a resolve hook for the transition to the app.private state. In terms of the router, this is a function that has to be resolved before the navigation commences.
  2. Inside this hook, we use the AuthFactory.isAuthenticated function that we defined earlier. However, for resolve to work as expected, the return value of the hook needs to be a promise method. Thus, we use $q to return a when resolution if the user is logged in and a reject event if they are not.
  3. If the user is not logged in, we use $state in order to tell the router to redirect the control to the home page again.

Finally, all we need to do is add an actual login screen for the app. To do so, start by adding a new file to keep controllers for our app at the path www/js/controllers.js. Make sure that this file has the following content:

angular.module('secureApp.controllers', ['secureApp.services'])
.controller('AppCtrl', function ($scope, $ionicModal, $timeout, AuthFactory) {
  // Form data for the login modal
  $scope.loginData = {};
  // Create the login modal that we will use later
  $ionicModal.fromTemplateUrl('templates/login.html', {
    scope: $scope
  }).then(function (modal) {
    $scope.modal = modal;
  });
  // Triggered in the login modal to close it
  $scope.closeLogin = function () {
    $scope.modal.hide();
  };
  // Open the login modal
  $scope.login = function () {
    $scope.modal.show();
  };
  // Perform the login action when the user submits the login form
  $scope.doLogin = function () {
    AuthFactory.login($scope.loginData.username, $scope.loginData.password)
    .then(function () {
      $scope.closeLogin();
    });
  };
});

To render the login screen itself, add a template for to the path www/templates/login.html:

<ion-modal-view>
  <ion-header-bar>
    <h1 class="title">Login</h1>
    <div class="buttons">
      <button class="button button-clear"
      ng-click="closeLogin()">
        Close
      </button>
    </div>
  </ion-header-bar>
  <ion-content>
    <form ng-submit="doLogin()">
      <div class="list">
        <label class="item item-input">
          <span class="input-label">
            Username
          </span>
          <input type="text"
          ng-model="loginData.username">
        </label>
        <label class="item item-input">
          <span class="input-label">
            Password
          </span>
          <input type="password"
          ng-model="loginData.password">
        </label>
        <label class="item">
          <button class="button button-block button-positive"
          type="submit">
            Log in
          </button>
        </label>
      </div>
    </form>
  </ion-content>
</ion-modal-view>

Finally, let's tie everything together by making sure that the app loads our newly defined controller. Load it in index.html:

<script src="js/controllers.js"></script>

Next, make sure that it is listed as a dependency in app.js:

angular.module('secureApp',
[
  'ionic',
  'secureApp.services',
  'secureApp.controllers'
])

We are now building our app. You can try it out by running it yourself. Try logging in with wrong credentials (according to the ones that we defined) in order to convince yourself that the app really blocks the user from going where they should not.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.41.229