Firebase Authentication “sticky” in Ionic


#1

I am trying to incorporate $firebaseAuth into my Ionic project. I used a sample example, logging in with Twitter (auth.$authWithOAuthPopup('twitter')), from the Firebase website and incorporated it into my Ionic Framework. The code is shown below.

I am using the following versions (everything is up to date as of today): firebase 2.0.4 angularfire 0.9.1 ionic 1.0.0-beta.14 “magnesium-mongoose”

Clicking on the button “Login” correctly opens the popup window and I can login. After that, however, in the Ionic project nothing has changed, while actually being logged in. When I refresh my browser, then it shows my displayname and it also notifies me that I am correctly logged in.

I had the same “stickyness” when trying to incorporate the ui-router authentication of Firebase, also copying the code from the website. Moreover, I had to refresh for changes to be processed.

Why is there this delay? Isn’t firebase supposed to be real-time? My guess is that it has to do something with the settings of Ionic, since using the Angularfire-seed (without Ionic) works perfectly fine. Or maybe its something else?

app.js

// Ionic Starter App

// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'
// 'starter.services' is found in services.js
// 'starter.controllers' is found in controllers.js
angular.module('starter', ['ionic', 'firebase', 'starter.controllers', 'starter.services'])

.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) {

  // Ionic uses AngularUI Router which uses the concept of states
  // Learn more here: https://github.com/angular-ui/ui-router
  // Set up the various states which the app can be in.
  // Each state's controller can be found in controllers.js
  $stateProvider

  // setup an abstract state for the tabs directive
    .state('tab', {
    url: "/tab",
    abstract: true,
    templateUrl: "templates/tabs.html"
  })

  // Each tab has its own nav history stack:

  .state('tab.dash', {
    url: '/dash',
    views: {
      'tab-dash': {
        templateUrl: 'templates/tab-dash.html',
        controller: 'DashCtrl'
      }
    }
  })

  .state('tab.chats', {
      url: '/chats',
      views: {
        'tab-chats': {
          templateUrl: 'templates/tab-chats.html',
          controller: 'ChatsCtrl'
        }
      }
    })
    .state('tab.chat-detail', {
      url: '/chats/:chatId',
      views: {
        'tab-chats': {
          templateUrl: 'templates/chat-detail.html',
          controller: 'ChatDetailCtrl'
        }
      }
    })

  .state('tab.friends', {
      url: '/friends',
      views: {
        'tab-friends': {
          templateUrl: 'templates/tab-friends.html',
          controller: 'FriendsCtrl'
        }
      }
    })
    .state('tab.friend-detail', {
      url: '/friend/:friendId',
      views: {
        'tab-friends': {
          templateUrl: 'templates/friend-detail.html',
          controller: 'FriendDetailCtrl'
        }
      }
    })

  .state('tab.account', {
    url: '/account',
    views: {
      'tab-account': {
        templateUrl: 'templates/tab-account.html',
        controller: 'AccountCtrl'
      }
    }
  });

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

});

services.js

angular.module('starter.services', [])

// let's create a re-usable factory that generates the $firebaseAuth instance
.factory("Auth", ["$firebaseAuth", function($firebaseAuth) {
  var ref = new Firebase("https://cloakit.firebaseio.com/");
  return $firebaseAuth(ref);
}])

controllers.js

// other controllers

// AccountCtrl
.controller("AccountCtrl", ["$scope", "Auth", function($scope, Auth) {
  $scope.settings = {
    enableFriends: true
  };

  $scope.auth = Auth;
  $scope.user = $scope.auth.$getAuth();
}]);

tab-account.html

<ion-view view-title="Account">
  <ion-content>
    <ion-list>
      <ion-item class="item-toggle">
        Enable Friends
        <label class="toggle">
          <input type="checkbox" ng-model="settings.enableFriends">
          <div class="track">
            <div class="handle"></div>
          </div>
        </label>
      </ion-item>
      <ion-item>
        <div ng-show="user">
      <p>Hello, {{ user.twitter.displayName }}</p>
      <button ng-click="auth.$unauth()">Logout</button>
    </div>
    <div ng-hide="user">
      <p>Welcome, please log in.</p>
      <button ng-click="auth.$authWithOAuthPopup('twitter')">Login</button>
    </div>
      </ion-item>
    </ion-list>
  </ion-content>
</ion-view>

index.html

<!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></title>

    <link href="lib/ionic/css/ionic.css" rel="stylesheet">
    <link href="css/style.css" rel="stylesheet">

    <!-- IF using Sass (run gulp sass first), then uncomment below and remove the CSS includes above
    <link href="css/ionic.app.css" rel="stylesheet">
    -->



    <!-- ionic/angularjs js -->
    <script src="lib/ionic/js/ionic.bundle.js"></script>

        <!-- firebase js -->
    <script src="lib/firebase/firebase.js"></script>
    <script src="lib/firebase/angularfire/angularfire.min.js"></script>

    <!-- loading bar -->
    <script src="lib/angular-loading-bar/src/loading-bar.js"></script>
    <link href='lib/angular-loading-bar/src/loading-bar.css' rel='stylesheet' />


    <!-- cordova script (this will be a 404 during development) -->
    <script src="lib/ngCordova/dist/ng-cordova.js"></script>
    <script src="cordova.js"></script>

    <!-- your app's js -->
    <script src="js/app.js"></script>
    <script src="js/controllers.js"></script>
    <script src="js/services.js"></script>
  </head>
  <body ng-app="starter">
    <!--
      The nav bar that will be updated as we navigate between views.
    -->
    <ion-nav-bar class="bar-stable">
      <ion-nav-back-button>
      </ion-nav-back-button>
    </ion-nav-bar>
    <!--
      The views will be rendered in the <ion-nav-view> directive below
      Templates are in the /templates folder (but you could also
      have templates inline in this html file if you'd like).
    -->
    <ion-nav-view></ion-nav-view>
  </body>
</html>

#2

On the face of it, it all looks OK.

Can you console.log($scope.user) at the start of your Controller to see what is in it? I think perhaps it is the caching in Beta 14 that might mean the Controller spool up code is not being called each time you open the view?

Maybe try disabling the caching on your views to see if you get it. Even so, with caching on, you should still get the contents of $scope.user preserved and can be reused. I believe that AngularFire will cache the objects itself, to try and prevent lots of roundtrip hits on the Firebase server for the same data.


#3

NB: I also found that .authWithOAuthPopup() was problematic. It seemed the callback routine was not being consistently called as I would have expected.

I ended up setting up an .onAuth() watcher that would ALWAYS invoke the callbacks whenever my user logged in or out of my app. Perhaps change your strategy to rely on .onAuth() to detect the actual login/logout rather than the callbacks from .authWithOAuthxxx() ??

EDIT: I just ran through your HTML again, and notice that you didn’t specify any callbacks with the .authWithOAuth… call on the Login button. I think that is why the authData is not being populated correctly. I’d strongly suggest you add in the .onAuth() watcher to detect logins and populate authData upon callback.


#4

Hello Cyber. Thank you for the help. I am not sure getting what to do? Do you have some snippets of your code?

I tried putting this in my service Auth:

// wrap the $onAuth function with $timeout so it processes
    // in the digest loop.
    onAuth: function onLoggedIn(callback) {
      auth.$onAuth(function(authData) {
        $timeout(function() {
          callback(authData);
        });
      });
    }

And then inside my

.run(function($ionicPlatform, Auth) {
      //ionic init code    

      //stateChange event
      $rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams){
      if (toState.authRequired && !Auth.onAuth()){ //Assuming the AuthService holds authentication logic
        // User isn’t authenticated
        $state.transitionTo("tab.account");
        event.preventDefault(); 
      }
    });
}

#5

hey Sibi, ever figured this out? i am struggling with the same issue…


#6

Not yet… I have switched to using $stateChangeError and or the following tutorial:


#7

@sibizavic1 @arjand just curious how long you find the firebase authentication token lasts in your Ionic apps? I’m using simple email login but find the auth token gets destroyed after a couple hours and I keep having to reauthenticate. This is really annoying as my app doesn’t contain any data requiring authentication every use like a banking app (its more like facebook or something that I’m willing to rely on the phone’s security for auth).