Authentication for Ionic Apps

In a previous post, I wrote about updating Vardyger's Admin UI in order to add support for internationalisation (i18n) and localisation (l10n). In this post, we'll update the Admin UI in order to add support for authentication.

The Cookie Monster

There are two common approaches to implementing server-side authentication for single page applications (SPAs): cookie-based authentication; and token-based authentication.

The cookie-based approach (which uses server-side cookies to authenticate the user on every request) is the most common, although it is rapidly being replaced by token-based authentication (which relies on a signed token that is sent to the server on each request).

Note: To learn more about the pros and cons of each approach, check out the links in the "References" section at the bottom of this post.

I'm going to start out by using the angular-http-auth module, the angular-local-storage module and the angular-mocks module (to simulate responses from Vardyger's RESTful API), before I take a detailed look at both token-based authentication and cookie-based authentication in subsequent posts.

Install angular-http-auth

angular-http-auth is an AngularJS module that makes it easy to add support for authentication to your Ionic apps.

To install angular-http-auth, enter the following command:

bower install --save angular-http-auth

We also want to take advantage of the angular-local-storage and the angular-mocks modules, so let's install them too:

bower install --save angular-local-storage
bower install --save-dev angular-mocks

Note: If you use --save-dev Bower will add the module to the devDependencies array in your project's bower.json.

Update index.html

We need to update index.html so that it includes the angular-http-auth, the angular-local-storage and the angular-mocksscripts:

...

<!-- bower:js -->
...
<script src="bower_components/angular-http-auth/src/http-auth-interceptor.js"></script>
<script src="bower_components/angular-local-storage/dist/angular-local-storage.js"></script>
<!-- endbower -->

<script src="bower_components/angular-mocks/angular-mocks.js"></script>

...

Update app.js

First, we need to inject the angular-http-auth (http-auth-interceptor), the angular-local-storage (LocalStorageModule) and the angular-mocks (ngMockE2E) modules into our app:

...

angular.module('vardyger', [
  'ionic',                  // inject the Ionic framework
  'http-auth-interceptor',  // inject the angular-http-auth module
  'LocalStorageModule',     // inject the angular-local-storage module
  'ngMockE2E',              // inject the angular-mocks module
  'pascalprecht.translate'  // inject the angular-translate module
])
  .config(function($ionicConfigProvider, $stateProvider,
    $urlRouterProvider, $translateProvider) {

      ...

Next, we need to define a new nested state, app.welcome:

    .state('app.welcome', {
      url: '/welcome',
      cache: false,
      views: {
        'menuContent': {
          templateUrl: 'templates/welcome-template.html',
        }
      }
    })

  // if none of the above states are matched, use this as the fallback
  $urlRouterProvider.otherwise('/app/welcome');

That will manage the Admin UI's new "Welcome" screen and serve as the apps fallback route. In English:

In German:

We also need to update the run function:

.run(function($ionicPlatform, $httpBackend, localStorageService) {

  ...

  $httpBackend.whenGET('https://posts')
    .respond(function (method, url, data, headers) {
      var authToken = localStorageService.get('authorizationToken');
      return authToken ? [200, posts] : [401];
    });

  $httpBackend.whenPOST('https://login')
    .respond(function(method, url, data) {
      var authToken = 'NjMw ...';
      return [200 , { authorizationToken: authToken } ];
    });

  $httpBackend.whenPOST('https://logout')
    .respond(function(method, url, data) {
      return [200];
    });

  $httpBackend.whenGET(/.*/).passThrough();
}

Which is where we define our mocks (by using $httpBackend from the angular-mocks module) for our $http requests.

Update the MainController

User's can navigate to the "Content" screen from the side-menu:

We need to update the "Content" screen's MainController (scripts/controllers/main-controller.js):

...

$http.get('https://posts')
  .success(function (data, status, headers, config) {
    $scope.listItems = data;
  })
  .error(function (data, status, headers, config) {
    $log.error('An error occurred: ' + status);
  });

....

Now, when a user chooses the "Content" item (from the side-menu) the MainController will make a HTTP request and because the user has not been authenticated, the request will return a response with a HTTP status code of 401. The 401 will be intercepted by the angular-http-auth module's “authService” and an event called event:auth-loginRequired will be broadcast.

That means we also need to create a LoginController (scripts/controllers/login-controller.js):

...

$scope.$on('event:auth-loginRequired', function(e, rejection) {
  $scope.loginModal.show();
});

...

That provides an event handler for the auth-loginRequired event, that prompts for the user's credentials:

When the user has logged in successfully, the previous HTTP request (for “posts”) will be resent by the authService and the appropriate success/error block will be executed:

Update the side-menu template

We need to update the side-menu template (templates/side-menu-template.html:

<ion-side-menus enable-menu-with-back-views="false">

    ...

  <ion-side-menu side="left">
    <ion-content class="has-header dark-bg">
      <ion-list>

        ...

        <ion-item menu-close class="item-icon-left item-dark" 
          ng-click="logout()" ng-show="isLoggedIn()">
          {{ 'LOGOUT' | translate }}
        </ion-item>

      </ion-list>
    </ion-content>
  </ion-side-menu>
</ion-side-menus>

So, that it includes a "Logout" menu item:

Note: ng-show and the AppController's isLoggedIn() function (see below) are used to show/hide the "Logout" menu item.

We also need to update the side-menu template's AppController (scripts/controllers/app-controller.js):

Create the Authentication Service

Finally, we need to create the AuthenticationService (scripts/services/authentication-service.js):

References: