Authentication with Ionic and Angular.js in a Cordova/Phonegap mobile web application

There are many different approaches to authentication in general. One size certainly does not fit all. However, I will explain one approach to solve this problem using the ionic framework and Angular.js.

First, we need to discuss how the backend web service will handle authentication. One common approach is for the backend web service to require that an auth token be sent with each resource request. To obtain an auth token, the client will make a login request with a username and password. The web service will authenticate the username and password and if the authentication is successful, it will generate an auth token and place it in the response back to the client.

All resource requests from the client will require the auth token to be placed on the HTTP request header with the “Authentication” key. The auth token will be checked to make sure it is still valid and has not expired. If the auth token is invalid or has expired, an HTTP response with status of 401 will be returned back to the client.

On the client side, once we get an auth token from a successful login, we save it locally so that we can pass it to the backend on subsequent resource requests. We really don’t want to have to keep track of whether or not the auth token has expired. In fact, what would really be great is if our client application could detect when a user needs to login, force the user to login, and then after login is successful, just carry on where it left off like nothing ever happened. Sounds good, right? So how do we implement this kind of behavior in a somewhat unobstrusive way? Well, with a little work and some help from an external library and the way Angular.js works, we can achieve that.

Witold Szczerba created a wonderful Angular.js module called angular-http-auth that will help us create an elegant solution. Angular.js has a concept of “interceptors”. The angular-http-auth module provides an “authService” that uses an interceptor to intercept HTTP requests and responses. If an HTTP response returns a status of 401, the “authService” will broadcast an event indicating login is required. Our application can then listen for this event and open a login modal allowing the user to login. Once the user has logged in successfully, we can let the “authService” know that login has been confirmed. The “authService” will then re-issue the previous HTTP request and execute the success/error/finally handlers associated to the original request. How awesome is that? That is exactly what we want!

So let’s build an Ionic mobile web application using the angular-http-auth library. You can build an ionic starter app and then fill in the blanks with the files below or you can download the complete project at ionic-http-auth

Our app will have a left side menu with “Home”, “Customers” and “Logout” links. The default landing page will be the “Home” page. For simulation purposes, we are going to use $httpBackend from the ngMockE2E mocking framework. We define this in our app.js file below.

app.js


angular.module('ionic-http-auth', ['ionic', 'ngMockE2E', 'ionic-http-auth.services', 'ionic-http-auth.controllers'])

.run(function($rootScope, $ionicPlatform, $httpBackend, $http) {  
  // Mocking code used for simulation purposes (using ngMockE2E module)	
  var authorized = false;
  var customers = [{name: 'John Smith'}, {name: 'Tim Johnson'}];
  
  // returns the current list of customers or a 401 depending on authorization flag
  $httpBackend.whenGET('https://customers').respond(function (method, url, data, headers) {
     return authorized ? [200, customers] : [401];
  });

  $httpBackend.whenPOST('https://login').respond(function(method, url, data) {
    authorized = true;
    return  [200 , { authorizationToken: "NjMwNjM4OTQtMjE0Mi00ZWYzLWEzMDQtYWYyMjkyMzNiOGIy" } ];
  });

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

  // All other http requests will pass through
  $httpBackend.whenGET(/.*/).passThrough();
})

.config(function($stateProvider, $urlRouterProvider) {

  $stateProvider
  
    .state('app', {
      url: "/app",
      abstract: true,
      templateUrl: "templates/menu.html",
      controller: 'AppCtrl'
    })
    .state('app.home', {
      url: "/home",
      views: {
        'menuContent' : {
          controller:  "HomeCtrl",
          templateUrl: "templates/home.html"
        }
      }      	  
    })
    .state('app.customers', {
      url: "/customers",
      views: {
        'menuContent' : {
          controller:  "CustomerCtrl",
          templateUrl: "templates/customers.html"            	
        }
      }      	  
    })
    .state('app.logout', {
      url: "/logout",
      views: {
        'menuContent' : {
         controller: "LogoutCtrl"
        }
      } 
    });
  $urlRouterProvider.otherwise("/app/home");
});


index.html

In the index.html, in addition to the normal includes, we will include the http-auth-interceptor.js from angular-http-auth.
The application will be placed in the ion-nav-view tag.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
    <title>Home</title>
 
    <!-- ionic css -->
    <link rel="stylesheet" href="lib/ionic/css/ionic.css">
     
    <!-- your app's css -->
    <link rel="stylesheet" href="css/style.css" >
 
    <!-- ionic/angularjs scripts -->
    <script src="lib/ionic/js/ionic.bundle.js"></script>
    <script src="lib/ionic/js/angular/angular-mocks.js"></script>
     
    <!-- http-auth-interceptor to automatically detect login required -->
    <script src="lib/angular-http-auth/http-auth-interceptor.js"></script>
 
    <!-- cordova script (this will be a 404 during development) -->
    <script src="cordova.js"></script>
     
    <!-- your app's script -->
    <script src="js/app.js"></script>
    <script src="js/services.js"></script>
    <script src="js/controllers.js"></script>
  </head>
 
  <!--
    'ionic-http-auth' is the name of this angular module (js/app.js)
  -->
  <body ng-app="ionic-http-auth" animation="slide-left-right-ios7">
		<ion-nav-view></ion-nav-view>
  </body>
</html>

In the “menu.html” file, we will define the side menu and where the content for the menu links will go.

menu.html

<ion-side-menus>

  <ion-pane ion-side-menu-content>
    <ion-nav-bar class="bar-stable nav-title-slide-ios7">
      <ion-nav-back-button class="button-clear"><i class="icon ion-chevron-left"></i> Back</ion-nav-back-button>
    </ion-nav-bar>
    <ion-nav-view name="menuContent" animation="slide-left-right"></ion-nav-view>
  </ion-pane>

  <ion-side-menu side="left">
    <header class="bar bar-header bar-stable">
      <h1 class="title">Menu</h1>
    </header>
    <ion-content class="has-header">
      <ion-list>
        <ion-item nav-clear menu-close ui-sref="app.home">
          Home
        </ion-item>
        <ion-item nav-clear menu-close ui-sref="app.customers">
          Customers
        </ion-item>
        <ion-item nav-clear menu-close ui-sref="app.logout">
          Logout
        </ion-item>
      </ion-list>
    </ion-content>
  </ion-side-menu>
  
</ion-side-menus>

The “AppCtrl” will be responsible for loading our login modal.
The “LoginCtrl” will be responsible for allowing the user to login. The “CustomerCtrl” will be responsible for fetching the customers from a backend web service. The “LogoutCtrl” will be responsible for allowing the user to logout.

controllers.js


angular.module('ionic-http-auth.controllers', [])
.controller('AppCtrl', function($scope, $state, $ionicModal) {
   
  $ionicModal.fromTemplateUrl('templates/login.html', function(modal) {
      $scope.loginModal = modal;
    },
    {
      scope: $scope,
      animation: 'slide-in-up',
      focusFirstInput: true
    }
  );
  //Be sure to cleanup the modal by removing it from the DOM
  $scope.$on('$destroy', function() {
    $scope.loginModal.remove();
  });
})
  
.controller('LoginCtrl', function($scope, $http, $state, AuthenticationService) {
  $scope.message = "";
  
  $scope.user = {
    username: null,
    password: null
  };
 
  $scope.login = function() {
    AuthenticationService.login($scope.user);
  };
 
  $scope.$on('event:auth-loginRequired', function(e, rejection) {
    $scope.loginModal.show();
  });
 
  $scope.$on('event:auth-loginConfirmed', function() {
     $scope.username = null;
     $scope.password = null;
     $scope.loginModal.hide();
  });
  
  $scope.$on('event:auth-login-failed', function(e, status) {
    var error = "Login failed.";
    if (status == 401) {
      error = "Invalid Username or Password.";
    }
    $scope.message = error;
  });
 
  $scope.$on('event:auth-logout-complete', function() {
    $state.go('app.home', {}, {reload: true, inherit: false});
  });    	
})
 
.controller('HomeCtrl', function($ionicViewService) {
 	// This a temporary solution to solve an issue where the back button is displayed when it should not be.
 	// This is fixed in the nightly ionic build so the next release should fix the issue
 	$ionicViewService.clearHistory();
})

.controller('CustomerCtrl', function($scope, $state, $http) {
    $scope.customers = [];
    
    $http.get('https://customers')
        .success(function (data, status, headers, config) {
            $scope.customers = data;
        })
        .error(function (data, status, headers, config) {
            console.log("Error occurred.  Status:" + status);
        });
})
 
.controller('LogoutCtrl', function($scope, AuthenticationService) {
    AuthenticationService.logout();
})

When the user selects the “Customers” link from the left side navigation menu, an HTTP request will be issued to get the customers. Since the user has NOT been authenticated, the response will return a status of 401. This status will be intercepted by the “authService” and an event called “event:auth-loginRequired” will be broadcast. In our “LoginCtrl”, we will provide an event handler for that event and show the “loginModal” when that event occurs.

When the user submits the request to login, we provide a special option on the config called “ignoreAuthModule”. By setting this to true, we are telling the “authService” to ignore a 401 status returned by this login request.

Once the user logs in successfully, the following takes place:

1. The authToken received from the backend web service is placed on the header.
2. We inform the “authService” that login was confirmed and provide a function to be executed on the previous request config which
will set the authToken on the header for the previous request.
3. An event called “event:auth-loginConfirmed” will be broadcast by the “authService”. We respond to that event in the “LoginCtrl”
by hiding the “loginModal”. Our previous request for “customers” will be resent by the “authService” and the appropriate
success/error/finally block will be executed as if nothing ever happened.

If the user login attempt fails, we broadcast an event called “event:auth-login-failed”. We respond to this event in the “LoginCtrl” by just displaying an error message.

When the user selects the “Logut” link, a call is made to the backend web service to logout. Then the authToken is removed from the header and an event called “event:auth-logout-complete” is broadcast. The “LoginCtrl” will respond to this event by navigating the user to the “Home” page and show the “loginModal”.

services.js


angular.module('ionic-http-auth.services', ['http-auth-interceptor'])
.factory('AuthenticationService', function($rootScope, $http, authService, $httpBackend) {
  var service = {
    login: function(user) {
      $http.post('https://login', { user: user }, { ignoreAuthModule: true })
      .success(function (data, status, headers, config) {

    	$http.defaults.headers.common.Authorization = data.authorizationToken;  // Step 1
        // Store the token in SharedPreferences for Android, and Keychain for iOS
        // localStorage is not very secure
        
    	// Need to inform the http-auth-interceptor that
        // the user has logged in successfully.  To do this, we pass in a function that
        // will configure the request headers with the authorization token so
        // previously failed requests(aka with status == 401) will be resent with the
        // authorization token placed in the header
        authService.loginConfirmed(data, function(config) {  // Step 2 & 3
          config.headers.Authorization = data.authorizationToken;
          return config;
        });
      })
      .error(function (data, status, headers, config) {
        $rootScope.$broadcast('event:auth-login-failed', status);
      });
    },
    logout: function(user) {
      $http.post('https://logout', {}, { ignoreAuthModule: true })
      .finally(function(data) {
        delete $http.defaults.headers.common.Authorization;
        $rootScope.$broadcast('event:auth-logout-complete');
      });			
    },	
    loginCancelled: function() {
      authService.loginCancelled();
    }
  };
  return service;
})

login.html

<div class="modal" ng-controller="LoginCtrl">
    <ion-header-bar class="bar-stable">
   	  <h1 class="title">Login</h1>
    </ion-header-bar>	
    <ion-content has-header="true" padding="true">
      <form class="list">
        <div class="item item-divider">
          Just tap the login button to simulate a successful login
        </div>
        <div>{{message}}</div>
        <label class="item item-input">
          <input type="text" placeholder="Username" ng-model="user.username" required>
        </label>
        <label class="item item-input">
          <input type="password" placeholder="Password" ng-model="user.password" required>
        </label>
        <div class="padding">
          <button class="button button-block button-stable" ng-click="login()">Login</button>
        </div>
      </form>
    </ion-content>
</div>

customers.html

<ion-view title="Customers">
  <ion-nav-buttons side="left">
    <button menu-toggle="left"class="button button-icon icon ion-navicon"></button>
  </ion-nav-buttons>
  <ion-content padding="true">
    <div class="list">
      <a ng-repeat="customer in customers" class="item">
        <h2>{{ customer.name }}</h2>
      </a>
    </div>
  </ion-content>
</ion-view>

home.html

<ion-view title="Home">
  <ion-nav-buttons side="left">
    <button menu-toggle="left"class="button button-icon icon ion-navicon"></button>
  </ion-nav-buttons>
  <ion-content padding="true">
  
    <p>Welcome to the ionic-http-auth home page!</p>
    <p>Select the menu icon at the top of the page and select the Customers link.</p>
    <p>You will then be prompted to login.  You can just tap the login button to be logged in.</p>
    <p>You will then see the customers displayed.</p>
    
  </ion-content>
</ion-view>

So, without too much effort, we are able to provide a fairly transparent authentication solution. Remember, one size does not fit all, and your mileage may vary.

111 thoughts on “Authentication with Ionic and Angular.js in a Cordova/Phonegap mobile web application

    1. keithdmoore Post author

      You could write your own backend authentication, however, I wouldnt recommend it. Depending on what your backend service is written in, you should be able to find something that works the way I have described or at least similar. You might look at CAS – Central Authentication Service. You might also consider an OAuth2 solution as well. My article was more focused on the client side.

  1. Pingback: Best of JavaScript, HTML & CSS – Week of March 17, 2014 | Flippin' Awesome

  2. Sebastian

    Thanks for that article!

    I searched for the “AuthenticationService” but couldn’t find it. Where can I find the source code?

  3. Chris Scott

    I’m a noob Ionic / Angular, but this tutorial as-is didn’t work for me.

    using latest ionic.

    I had to reverse the load-order of services & controllers.

    Also, you store your templates in /view folder. That messed me up, since a starter app uses /templates.

    I ended up having success by not copy/pasting your code, but assembling pain-stakingly block-by-block.

    1. keithdmoore Post author

      Sorry you had so much trouble. I forgot about the folder change for the views/templates. I plan on putting a project together for this on Github. I hope you at least learned something about ionic and angular in the process.

  4. Sebastian

    Great article!
    A nice addition would be to describe how the server generated token could be created in a way that allows full scalability (saving the token in a database would make it a bottleneck in large scaled applications). Can anybody help with this?

    1. keithdmoore Post author

      It is a pretty language specific task. I disagree with your statement regarding the saving of the token in the database. If performance is an issue, you can use a cache for the token. How you implement this is very dependent on the language and environment you are using.

  5. Steve Williamson

    I am also an Ionic/AngularJS noob, but I couldn’t get the code to run as is.

    I had to change the first line of controllers.js to:

    angular.module(‘ionic-http-auth.controllers’, [])

    Also, on line 15 make sure that you have the correct path to signin.html. I had placed mine in /templates rather than /views

    1. keithdmoore Post author

      Thanks for the reply. I will correct those issues. I am working on getting a project setup on GitHub. I also plan to write some more articles related to translations and generating error messages. I am going to incorporate those into the GitHub project as well.

  6. Pingback: AngularJS | Pearltrees

  7. keithdmoore Post author

    I have fixed the issues reported previously. Also, as promised, I have put the code for the project in Github, You can see it here. I hope this helps previous readers as well as future readers.

  8. Sean Hill

    Curious, let’s say I ran this app on my mobile phone. What happens if the user closes their app and reopens it? Is it going to ask for their login again? That’d be extremely annoying to have to login every time they opened the app.

    1. keithdmoore Post author

      Agreed, that would be annoying. I was presenting a more conservative example. To solve this problem, upon a successful login, you could store the authorization token in localStorage for example. Then you could check to see if it exists maybe in the .run block in app.js. If it does, you could place it on the $http headers. If not, you could either take the user to the signin modal or just wait until they make a request that needs authorized access. I hope that helps. Let me know if that doesn’t make sense.

  9. Emile

    Great article, thanks! Couldn’t find anything else like it so glad you wrote it!

    There is just one little issue I can’t seem to get resolved or figured out…
    I use the left slide out menu (ionic start myApp sidemenu), which displays the “hamburger menu” in the top left corner which under certain circumstances (don’t quite understand how states work yet) shows a back button instead of the menu button.
    When logging out (rediredt to dashboard page with $state.go), the menu button and back button both show in the top left. The back button shouldn’t show here and I have no idea why it does!?

    Any ideas? Thanks!

    1. Emile

      Nevermind! Didn’t read properly! See you mention the issue in your home controller…
      Will wait for next ionic build to hopefully resolve this.

  10. ghprod

    Came here from ionic forum, and this article really blown my mind about how to setup cordova app with Auth function ..

    Thanks Keith, you really made my day 😀

  11. Sumit

    Great tutorial. May you please help me out to switch between views by clicking on a button. That means when i click on a button the view in the index.html should change. I have a button on home.html page, when i click on this button i should be able to see the one.html page. Please help me.

    1. keithdmoore Post author

      If you just wanted a link or button to a page that is in the menu, you could do this:

      <a ui-sref="app.customers" rel="nofollow">Customers Link</a>
      <button class="button" ui-sref="app.customers">Customers Button</button>
      

      If you wanted the page to be a nested state, you would need to configure that state appropriately in app.js. See angular-ui-router for the documentation for creating states and nested states, etc.

  12. kabir

    Hi after wracking brain for a day this tutorial nudged me in right direction thanks a lot . I have couple of question
    1). why are you injecting $http in LoginCtrl ??
    2). Wouldn’t it be better if events are listening on $rootScope instead of LoginCtrl’s $scope .

  13. keithdmoore Post author

    1). why are you injecting $http in LoginCtrl ??
    >> I just forgot to remove it. It is not needed in this controller.
    2). Wouldn’t it be better if events are listening on $rootScope instead of LoginCtrl’s $scope .
    >> I really don’t think it would be any better. The approach I used seems to be a pretty common pattern. A better approach might be to (inside LoginCtrl) put a $scope.$watch on an attribute in the AuthenticationService. Look at the documentation for $scope.$emit and $scope.$broadcast.

  14. kabir

    i can’t get it working if i put listeners on LoginCtrl’s $scope but using $rooScope it just works i think its some nesting issue . Can u give some code example for better approach you mentioned . Which attribute of AuthenticationService should i put $scope.$watch on.

    1. keithdmoore Post author

      After thinking about this a little more, I think it would be much more complicated to use the $scope.$watch approach. And it would really be of little benefit. Also, to use that approach, you would have to alter the http-auth module to have it set a variable. Then there would be a circular reference issue that you could get around with $injector. Starting to sound uglier and uglier. The only thing that might make that approach better would be saving the expense of broadcasting the event across the rootScope and down. You could use $rootScope to minimize the broadcast if you were to cancel it by using event.stopPropagation(). That might be better.

      So, the short answer is, you should probably just stick to using $rootScope and just add the event.stopPropagation() to the event listeners.
      For example:
      $rootScope.$on(‘event:auth-loginConfirmed’, function(event) {
      event.stopPropagation();
      $scope.username = null;
      $scope.password = null;
      $scope.loginModal.hide();
      }
      });

  15. JulienO

    hi , I have the error ” no more request expected” when i called my api authentification.
    login: function(user) {
    $http.post(‘MyURLapi/login’, { email : user.username, password : user.password }, { ignoreAuthModule: true })
    .success(function (response, message, headers, config) {
    $http.defaults.headers.common.Authorization = data.authorizationToken; // Step 1

    Have you any idea where it may come ? I work locally for now and my api server must return me { response : true, message : Connecté }
    thank you in advance

    1. keithdmoore Post author

      In app.js, you will need to change the $httpBackend mocks to match your http calls and have then return what you are expecting. Of course, these mocks would need to be removed when you use the actual end points.

      So in your case, you need to change the “login” mock to something like this:

      
        $httpBackend.whenPOST('MyURLapi/login').respond(function(method, url, data) {
          authorized = true;
          return  [200 , { response: true, message: "Connecté", authorizationToken: "WEzMDQtYWYyMjkyMzNiOGIy" } ];
        });
      
      1. JulienO

        thank you for your reply.
        I see, i must be remove this ? : $httpBackend.whenGET(‘UrlApi/login’).respond(function (method, url, data, headers) {return authorized ? [200, customers] : [401]; });

          1. JulienO

            it works half; It does not check the value of response and connect matter logins and password.

            I tried this but it does not change :
            $httpBackend.whenPOST(‘myapi/login’).respond(function(method, url, data) {
            if (response=true){
            authorized = true;
            return [200 , { response: true, message:””, authorizationToken: “NjMwNjM4OTQtMjE0Mi00ZWYzLWEzMDQtYWYyMjkyMzNiOGIy” } ];
            }
            else{
            authorized=false;
            }
            });

          2. keithdmoore Post author

            Not sure what you are doing with this “response” attribute. You are setting it to true in the if condition. I think you meant this “if (response == true)”.
            Where is the “response” variable defined and what are you using it for ?

          3. JulienO

            thank you for the correction.
            is it possible to enelevr httpbackend locally? How can I do to test with HTTP? I retrieves no information on the server side.
            Sorry I started in angularjs.

            Thank you in advance for your help,

  16. JulienO

    I misunderstood the usefulness of httpbackend. I want to recover data from my API without simulation now. it works great with :)

    1. umakanth

      Hi,
      I was stuck at the API Login. Could you please share your app.js code. I dont want to simulate this with mock. I want to login with a api. could you please help me. Thanks in advance.

    1. keithdmoore Post author

      The easiest thing to do in the sample app that I have provided would be to add this to the .run block:

      $rootScope.$broadcast(‘event:auth-loginRequired’);

      You could add an if block to that to check and see if they have already logged in, depending on your requirements.

      1. Yefei Wang

        Thanks for the article!
        I tried to broadcast an event of login required inside .run, but then the whole screen white out. Could you help me on this please? Thanks again!

  17. Marc D

    Hi,

    Thanks for your article ! Your script also works with the cookie ? May be with $ http .defaults. headers.common.Authorization = cookies.token; and ngCookies after login ? :)

  18. Ben

    Hey,
    Great article! thanks alot for the afford.

    I have a question, where/how do you maintain the token so that the application would remember the user is authorized even if we close the app and reopen?

    Thanks,
    Ben

    1. keithdmoore Post author

      You can either use localStorage or a cookie. I prefer to use localStorage. When a successful login occurs, get the token and put it in localStorage. You can always add it to your header by doing something like this depending on what your backend is expecting:

      $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w'

  19. sid

    Hi,
    Thanks for the article I have implemented the code and I am able to get response 200 from my API end point. But I am not able to toggle between login & my dashboard page. I have sessionStorage which stores user data after login. How to keep that check if user data exist or not. I am try to build app similar to google analytics on iOS.

    1. keithdmoore Post author

      I updated the ionic-http-auth project to use the ionic library to v1.0.0-beta.14 manually. Issuing the ionic command >$ ionic lib update only updates the ionic specific libraries.

  20. vinay

    I downloaded your repository and run command ” ionic lib update ” to update lib and after that it is not working like the way it works with old ionic.

    Like first time you click on customer , it is showing modal , but if you click on empty space around modal then modal gets hide.
    Also it is not showing up after clicking customer link again.

    1. keithdmoore Post author

      I updated the ionic-http-auth project to use the ionic library to v1.0.0-beta.14 manually. Issuing the ionic command >$ ionic lib update only updates the ionic specific libraries.

      1. vinay

        Hi,
        Thanks for fixing this.
        One more question.
        I added camera plugin in one page and it is working fine but after clicking photo when it comes back to the page , it reloads to homepage.
        Do you any cause of this problem?

        Thanks in advance

  21. Luca

    Hi, thank you for this article.
    I tried to use this logic in a project but I have a problem: even if my server returns a 401 header, when I check
    rejection.status
    it always is 404
    It seems that I’m not the only one fighting with this issue
    401 Never Being Caught by Interceptor
    The same happens with 500 header.
    Thanks in advance

    1. keithdmoore Post author

      I am not sure what the problem here is. I know that sometimes a 404 can mean that it was unable to reach the endpoint. Some common reasons, network unavailable, endpoint not found on the server, in some cases, the server may in fact be down. Have you inspected the request and response headers to see if there is any extra information on them?

      1. Luca

        Thanks for your quick answer. I checked the response and I think the headers from the server are right. Firebug and fiddler confirm the server answered with a 401 header. It seems to me that the only difference is between success and error. At line 12231 of ionic.bundle.js I see:

        callback = function(event) {
        removeEventListenerFn(script, "load", callback);
        removeEventListenerFn(script, "error", callback);
        rawDocument.body.removeChild(script);
        script = null;
        var status = -1;
        var text = "unknown";

        if (event) {
        if (event.type === "load" && !callbacks[callbackId].called) {
        event = { type: "error" };
        }
        text = event.type;
        status = event.type === "error" ? 404 : 200;
        }

        if (done) {
        done(status, text);
        }
        };

        From what I understand it seems that the status is set to 200 in case of success and to 404 in case of error, whatever the error code.
        I tried two different servers, both php. The php code I use for my tests is the following:

        header($_SERVER['SERVER_PROTOCOL'] . ' 401 Unauthorized', true, 401);
        die();

        I tried many variations of this code always with the same result.
        Thanks for your time

  22. SixDark

    Hi

    I downloaded the whole project from this site after I tried hours to add these code to my own example project.
    I don’t know what I doing wrong but for me it is not really working:

    Start the ‘normal’ login workflow for the first time. Here all works as expected (except the problem that the login dialog is not really shown modal).
    But then click logout and after this click customers again. You see the mocked data again without the need to login.
    Only when you completely reload the page the login works as expected again.

    What is the problem here?
    I expect that when I clicked logout and then customers again that it asks me for login (because I clicked logout before)…

    Also I see that the controllers.js is different at line 50. At the page here you wrote $state.go… and in the downloaded project there is only a console.log…

    Regards,
    SixDark

    1. keithdmoore Post author

      I corrected the problem when clicking on the Customers option after logging out. The reason this was a problem is because with beta14, the views are cached by default. As a quick fix, I disabled caching on the customers view. Also, the logout needed to be wrapped in an $ionicView.enter event so that the logout will occur. I could have disabled caching on this view as well but I decided this would demo this new feature. Good catch on the logout complete event handler. I corrected that as well. I also upgraded the side menu as well. Have a look at the Github repo to see the changes I made. I hope that helps you out.

      Cheers,

      Keith

      1. SixDark

        Hi Keith,

        thanks for your fast answer! Crazy…
        The first I think was there must something with caching but I am new to AngularJS/ionic and I could not found something helpful in the web.
        I played around and in a normal browser I found a workaround: add ‘$window.location.href = $location.protocol() + “://” + $location.host() + “:” + $location.port() + “/”;’ to the event ‘auth-logout-complete’ (this forces a redirect to Home and a complete refresh of the page). But this – for sure – was not working in an Android app.

        Your approach is working well also in an app! :-)

        So I can try now to replace all the mocked stuff with our API login.

        Thank you very much! Very good work! :-)

        Regards,
        SixDark

  23. Yefei Wang

    Thanks for the great article!

    I am exploring prompt to login at the start of app. I had this in the .run in app.js, and it seems that screen is blanked out.

    $rootScope.$broadcast(‘event:auth-loginRequired’);

    Could you take a look at this issue please and see if the same happens to you? Or maybe there is something else I need to add? Thanks!

    1. keithdmoore Post author

      Try replacing HomeCtrl with the following:

      
      .controller('HomeCtrl', function($rootScope, $scope, $timeout) {
        $scope.$on('$ionicView.enter', function() {
          $timeout(function(){
            $rootScope.$broadcast('event:auth-loginRequired');
          });
        });
      })
      
      
      1. Yefei Wang

        Thanks much! Mind if you briefly tell why this would work and putting the broadcast in .run doesn’t? Is that because of some scope or?

        1. keithdmoore Post author

          If you put it in the .run then you would need to do it as part of a state change or something along those lines.
          If you just put it there directly, it will run before the rest of the app has been loaded. Check the console log and you will see what I mean. You could do something like the below:

          
          $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){
            if ($toState.name == 'some.state.name') {     
              $timeout(function(){
                $rootScope.$broadcast('event:auth-loginRequired');
              });
            }
          })
          
          
        2. Tom Kennedy

          This fires every time you visit the home tab. How would you just get it to fire once upon initial successful login?

          1. Tom Kennedy

            In the above homectrl code the login modal shows when you navigate away and then navigate back. I am looking to do something similar. I want to make sure a login screen is presented on app start if user has not previously signed in.

            I want to do the following.
            1. Require login if token does not exist in local storage upon app start
            2. If token exists then let user through

            Have you any example on this? Great post!

          2. keithdmoore Post author

            Your best bet might be to add some code to the AppCtrl.

            
              $ionicModal.fromTemplateUrl('templates/login.html', function(modal) {
                  $scope.loginModal = modal;
                  // Check for token.  If not present, $scope.loginModal.show();
                },
                {
                  scope: $scope,
                  animation: 'slide-in-up',
                  focusFirstInput: true
                }
              );
            
            
  24. Luke Anderson

    Hi,
    This out-of-the-box solution works fine with the mock code but as soon as I change the GET url in Customers controller to my actual server url, I can see a 401 being returned in FireBug, but the 401 is not intercepted and the login does not display. Would you know why this would be happening? I am not very experienced in Angular/Ionic yet.

    Thanks!

    1. keithdmoore Post author

      I apologize for not responding sooner. I have been juggling several projects lately. I would have to look at your code to really determine your issue. You need to make sure you are not adding the ignoreAuthModule option when making your normal http calls. That option is only used on the login and logout methods. That may very well be your problem. You could put some log statements in the angular-http-auth httpInterceptor functions to see why the 401 is not being captured. I hope that helps.

  25. Edax

    Hi! thanks for the post, i’ve changed the code to connect with my web api, but i get this error

    Error: Unexpected request: GET templates/home.html
    No more request expected
    Error: Unexpected request: GET templates/login.html
    No more request expected
    Error: Unexpected request: GET templates/customers.html
    No more request expected
    ReferenceError: $state is not defined
    at http://localhost:8082/js/app.js:40:17
    Error: Unexpected request: GET templates/menu.html
    No more request expected
    at $httpBackend (http://localhost:8082/lib/ionic/js/angular/angular-mocks.js:1177:9)
    at sendReq (http://localhost:8082/lib/ionic/js/ionic.bundle.js:17408:9)
    at $get.serverRequest (http://localhost:8082/lib/ionic/js/ionic.bundle.js:17126:16)
    at processQueue (http://localhost:8082/lib/ionic/js/ionic.bundle.js:20962:27)
    at http://localhost:8082/lib/ionic/js/ionic.bundle.js:20978:27
    at Scope.$get.Scope.$eval (http://localhost:8082/lib/ionic/js/ionic.bundle.js:22178:28)
    at Scope.$get.Scope.$digest (http://localhost:8082/lib/ionic/js/ionic.bundle.js:21994:31)
    at Scope.$get.Scope.$apply (http://localhost:8082/lib/ionic/js/ionic.bundle.js:22282:24)
    at bootstrapApply (http://localhost:8082/lib/ionic/js/ionic.bundle.js:9266:15)
    at Object.invoke (http://localhost:8082/lib/ionic/js/ionic.bundle.js:11994:17)

  26. mary

    Hello ,
    And many thanks for the tutorial…
    I do have a problem, if you can help me aout, it would be great …
    When trying to login agains my own php server , I change only the url in the POST request in the authentiation service , changing with my own address.
    var service = {
    login: function(user) {
    $http.post(‘js/login.php’, { user: user }, { ignoreAuthModule: })

    The Post request does not get executed …What am I doeing wrong ? Please help :)Thank you ..

    1. keithdmoore Post author

      Looks like you need to do this:

      
      $http.post(‘js/login.php’, { user: user }, { ignoreAuthModule: true})
      
      

      Be sure to remove the mocking code as well. That url doesn’t look like a valid url. Not real familiar with php but it seems you need to call an actual url that is being hosted. Either locally or remote.

  27. mary

    ..thank you for your fast reply…

    I changed the POST code to the following:
    $http.post(‘http://localhost/test/login.php’, { user: user }, { ignoreAuthModule: true })

    When I click login, the error block is exexuted and I do not see anything while inspecting the http traffic.
    I am very new to angular…And when you say remove mock ing code, is the
    var authorized = false; from app.js on line 4 not imprtant ? And do I have to remove also the dependencies ‘ngMockE2E’ and $httpBackend ?
    I aplogize for my dumb questions, I hope I can get it working…
    I have my own customers.php file , and the communication with it seems to work fine , with the get request $http.get(‘customers.php’).
    I do not unserstand why I do not see any http traffic while clicking on Login button ….Please help :)
    …And thank you :):)

  28. mary

    Many many thanks :)
    …You helped me a lot ..it works …:)
    I hope I understood everything …I still had a problem while trying to access the token , but then I noticed that the token was accessed via json , not via the http headers ..
    Many many thanks , again .

  29. Mtnbrit

    Would be great if you could include the commonly needed localStorage of the auth token in your example for a fully useful auth template

  30. Pingback: Best of JavaScript, HTML & CSS – Week of March 17, 2014 - Modern Web

  31. David

    Didn’t work for me. I’m new in Ionic and I don’t understand the problem. Here is the error:

    Uncaught Error: [$injector:modulerr] Failed to instantiate module ionic-http-auth due to:
    Error: [$injector:modulerr] Failed to instantiate module ngMockE2E due to:
    Error: [$injector:nomod] Module ‘ngMockE2E’ is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
    http://errors.angularjs.org/1.3.13/$injector/nomod?p0=ngMockE2E
    at http://localhost:8100/lib/ionic/js/ionic.bundle.js:8755:12
    at http://localhost:8100/lib/ionic/js/ionic.bundle.js:10459:17
    at ensure (http://localhost:8100/lib/ionic/js/ionic.bundle.js:10383:38)
    at module (http://localhost:8100/lib/ionic/js/ionic.bundle.js:10457:14)
    at http://localhost:8100/lib/ionic/js/ionic.bundle.js:12789:22
    at forEach (http://localhost:8100/lib/ionic/js/ionic.bundle.js:9015:20)
    at loadModules (http://localhost:8100/lib/ionic/js/ionic.bundle.js:12773:5)
    at http://localhost:8100/lib/ionic/js/ionic.bundle.js:12790:40
    at forEach (http://localhost:8100/lib/ionic/js/ionic.bundle.js:9015:20)
    at loadModules (http://localhost:8100/lib/ionic/js/ionic.bundle.js:12773:5)

  32. Pingback: Mobile Web Weekly No.1 | ENUE Blog

  33. JavedH

    Hi,

    Great work.. Just need few clarification

    The username and password which is sent the backend, won’t it be easily hackable by middle men.

    does https call make it secure or are there other ways to encrypt them further.

    1. keithdmoore Post author

      There are several approaches and areas that can help. Here are a couple.
      1. Certificates should be used to prevent “man in the middle” attacks.
      2. Tokens should expire within a reasonable amount of time. For example, maybe 1 hour.

  34. Pingback: Basic Auth in ionic framework / AngularJS | Hosik. C.

  35. Umakanth

    Hello,
    I am trying to run your app in my local system. I have downloaded your project from github repo. I am not able to run the app as you said. It is showing me a blank page. Also, I coulnt find any lib folder as you have mentioned in you index.html. The below mentioned files are not available in the project. Could you please share these files if you have ASAP. Thanks in Advance.

  36. sukhsingh

    Hi, thank you this is a great article. I am very new to ionic and returning to coding after 10+ years of paper pushing.

    Is it possible for you to put an example that doesn’t use ngMockE2E and httpBackend. For example,
    Lets say I have a a JWT token based login api
    http://localhost/v1/authenticate?email=emailaddress&password=psss

    When a post request is used – it gives the following response
    {“token”:”some long token string”}

    and if you use v1/authenticate/user?token=the token above

    It returns

    {“user”:{“id”:”12″,”name”:”myname”,”email”:”emailaddress”,”created_at”:”2015-10-14 06:45:36″,”updated_at”:”2015-10-14 06:45:36″}}

    Thanks,
    Sukh

  37. Ratikanta Pradhan

    Thank you keithdmoore for the nice description.

    Can I do digest authentication here.

    I want to connect to http://180.87.230.91:8089/ODKAggregate/formList

    In Google chrome browser or any browser it is taking digest authentication.

    I want to do it in my ionic app.

    I can I do it by the help of this example. I know this example will help me but bit confused between $httpBackend and $http.

    Can you help me.

    Thank you.

    1. keithdmoore Post author

      `$http` is used to make a backend request to a server. `$httpBackend` is usually used for integrated testing. I am using it for demo purposes.
      I would recommend that you do a Google search for `digest authentication angularjs` and maybe try incorporating one of those libraries into your Ionic project.

Leave a Reply

Your email address will not be published. Required fields are marked *