angularFire - bug in angularFireAuth

I'm working on a project that utilises angularFire and it's new angularFireAuth function to authenticate users based on the Firebase Authentication system. While working on the project I have run into an issue that I have reported to the angularFire team, and in the post I will describe the problem as well as a proposed solution that fixes the issue - but still leaves one issue open.

To explain the problem, I'm going to be using the code from the official AngularJS documentation - the phone tutorial. The requirements of the application are:

  • Display a list of phones from the Firebase database
  • Clicking on the name of the phone, the user should be redirect to the phone detail page
  • If the user is authenticated, display the details of the phone, otherwise redirect to the login page

The router of the application looks like this:

angular.module('phonecat', ['firebase']).
 config(['$routeProvider', function($routeProvider) {
 $routeProvider.
      when('/phones', {
        templateUrl: 'partials/phone-list.html', 
        controller: PhoneListCtrl,
        authRequired: false,
        pathTo: '/phones'
      }).
      when('/phones/:age', {
        templateUrl: 'partials/phone-detail.html',
        controller: PhoneDetailCtrl,
        authRequired: true,
        pathTo: '/phones/:phoneId'
      }).
      when('/login', {
        templateUrl: 'partials/login.html',
        controller: LoginCtrl,
        authRequired: false,
        pathTo: '/login'
      }).
      otherwise({
        redirectTo: '/phones'
      });
}]);

And this is the controller:

'use strict';

function PhoneListCtrl($scope, angularFire, angularFireAuth) {
  var url = 'https://<link>.firebaseio.com/';
  var promise = angularFire(url, $scope, 'phones', []);
  angularFireAuth.initialize(url, {scope: $scope, name: "user"});
}

function PhoneDetailCtrl($scope, $routeParams, angularFire, angularFireAuth) {
  var url = 'https://<link>.firebaseio.com/' + $routeParams.age;
  angularFireAuth.initialize(url, {scope: $scope, path: "/login"});
  $scope.$on("angularFireAuth:login", function(evt, user) {
    var promise = angularFire(url, $scope, 'phone', {}); 
  });
  $scope.$on("angularFireAuth:logout", function(evt) {
    console.log("you are logged out.");
  });
  $scope.$on("angularFireAuth:error", function(evt, err) {
    console.log(err);
  });
}

function LoginCtrl($scope, angularFire, angularFireAuth) {
  var url = 'https://<link>.firebaseio.com';
  angularFireAuth.initialize(url, {scope: $scope});
  $scope.form = {};
  $scope.login = function() {
    console.log("called");
    var username = $scope.form.username;
    var password = $scope.form.password;
    angularFireAuth.login('password', {
        email: username,
        password: password,
        rememberMe: false
      });
  };
}

And finally here are some code snippets from the HTML files:

<div class="container-fluid">
    <div class="row-fluid">
      <div class="span4">
        <ul class="phones">
          <li ng-repeat="phone in phones">
            <a href="#/phones/{{phone.age}}">{{phone.id}}</a>
            <p>{{phone.snippet}}</p>
          </li>
        </ul>
      </div>
    </div>
  </div>

  {{user.email}} | <a ng-click="logout()">Logout</a>
  you are look looking at {{ phone.name }}
</span>
<span ng-hide="user">
  login pls!
</span>
<input type="text" name="username" ng-model="form.username"><br>
<input type="text" name="password" ng-model="form.password"><br>
<button name="login" id="login" ng-click="login()">login</button>

And of course index.html would have the app declaration, the path to all the required angular.js files as well as the right path to your app.js and controller.js files.

If you are wondering what is my data structure - I have imported the phones.json file into Firebase and it's original structure looked like this:

[{
    "age": 0, 
    "id": "motorola-xoom-with-wi-fi", 
    "imageUrl": "img/phones/motorola-xoom-with-wi-fi.0.jpg", 
    "name": "Motorola XOOM\u2122 with Wi-Fi", 
    "snippet": "The Next, Next Generation\r\n\r\nExperience the future with Motorola XOOM with Wi-Fi, the world's first tablet powered by Android 3.0 (Honeycomb)."
}, 
{
    "age": 1, 
    "id": "motorola-xoom", 
    "imageUrl": "img/phones/motorola-xoom.0.jpg", 
    "name": "MOTOROLA XOOM\u2122", 
    "snippet": "The Next, Next Generation\n\nExperience the future with MOTOROLA XOOM, the world's first tablet powered by Android 3.0 (Honeycomb)."
}]

Note Please don't forget to enable the correct Firebase authentication method and also add a user. Refer to this documentation for more information.

If you run the application, you'll see that clicking on the name of the phone would redirect you to the login page, but after putting in the right email address and password, instead of being redirected to the right path, you'll end up having an endless loop that loads /phone/:age, then /login, and /phone/:age again until the browser gives up (my browser crashed because I was doing some debugging in the JS console and the loop caused hundreds of entries.)

After spending some time on it, and some support from Anant, the developer behind angularFire, I found what the issue is and it all bogs down to some variable declarations in the angularFire.js code.

To get everything working there are a few things that you need to do. First of all, find the this._redirectTo = null; and this._authenticated = false; lines in the initialise function, and comment them out. Next, find the declaration of client and modify it so that it reads:

var client = new FirebaseSimpleLogin(this._ref, function(err, user) {
  self._cb(err, user);
    if (err) {
      $rootScope.$broadcast("angularFireAuth:error", err);
    } else if (user) {
        this._authenticated = true;
        this._redirectTo = $route.current.pathTo;
        self._loggedIn(user)
    } else {
        this._authenticated = false;
        self._loggedOut();
    }
  });
  this._authClient = client;
},

I have moved the previously commented out lines to the else if statement. If you retry now, you will see that after entering the right credentials you will be redirected to the correct page.

Note Please note that there is another currently open issue with angularFire - at the moment /phones/:id will actually redirect to /phones/:id and :id won't be replaced by the right id. Follow this issue on GitHub to learn more.

The only issue that is happening at the moment is if a particular user is logged in (i.e. there is a valid user object) and you navigate to /login, you won't be automatically redirected to a page nor you would be able to see the user object. I will think about this a bit more and try to come up with a neat solution. If you're interested in following this issue, please have a look at the progress on GitHub.

Show Comments