Search code examples
ember.jsfirebaseemberfire

Protecting a route using Firebase Simple Login


I'm trying to implement the following sequence of events in an Ember app that uses Firebase Simple Login and ember-cli.

  1. Check if the user is authenticated before allowing entry to any route. All routes need to be authenticated.
  2. If the user is not authenticated, redirect to LoginRoute. If the user is authenticated, allow them to enter the route.

In order to accomplish step 1, I reopen Ember.Route in an initializer and implement a beforeModel hook.

`import LoginController from "tracking/controllers/login"`

AuthInitializer =
  name: 'authInitializer'

  initialize: (container, application) ->
    # Register LoginController with all controllers/routes
    application.register 'main:auth', LoginController
    application.inject 'route', 'auth', 'main:auth'
    application.inject 'controller', 'auth', 'main:auth'
    application.inject 'main:auth', 'store', 'store:main'

    # Ensure user is logged in before allowing entry
    Ember.Route.reopen
      beforeModel: (transition) ->
        @transitionTo 'login' if !@get('auth.isAuthed')

`export default AuthInitializer`

The above code does indeed redirect to login if the user is not currently logged in.

LoginController simply instantiates a new FirebaseSimpleLogin object and registers the appropriate callback function.

LoginController = Ember.ObjectController.extend
  # Some of the controller is omitted for brevity...

  auth: null
  isAuthed: false

  init: ->
    dbRef = new Firebase('https://dawnbreaker.firebaseio.com')
    @set('auth', new FirebaseSimpleLogin(dbRef, @authCompleted.bind(@)))
    @_super()

  authCompleted: (error, user) ->
    if error
      # Handle invalid login attempt..
    else if user
      # Handle successful login..
      unless @get('isAuthed')
        @set('currentUserId', user.id)
        @set('isAuthed', true)
        @transitionToRoute('index')
    else
      # Handle logout..
      @set('currentUserId', null)
      @set('isAuthed', false)

`export default LoginController`

There is two problems with my implementation.

  • When LoginController first initializes, isAuthed is set to false. Therefore, when authCompleted finishes, the app will always redirect to index.
  • The beforeModel hook executes before the authCompleted callback finishes, causing the hook to redirect to login.

What results from a page refresh is

  1. The login template flashes for a second.
  2. The app transitions to index.

This causes every page refresh to lose its current location (redirecting to the index route).

The question is, how can I protect a route using Ember and Firebase Simple Login? Any help would be appreciated.


Solution

  • Totally my opinion, but I like making the auth part of the resource tree. It doesn't need to be part of the url, but can be. Using this way, it can still use a global controller (it will be hooked up based on what's returned from the call, or some other hookup if you fetch it in the login).

    App.Router.map(function() {
      this.resource('auth', {path:''}, function(){
        this.resource('foo');
        this.resource('bar', function(){
          this.route('baz')
        });
      });
      this.route('login');
    });
    
    App.AuthRoute = Em.Route.extend({
      model: function(params, transition){
        var self = this;
        // you can skip calling back with a local property
        return $.getJSON('/auth').then(function(result){
          if(!result.good){
            self.transitionTo('login');
          }
          return result;
        });
      }
    }); 
    

    http://emberjs.jsbin.com/OxIDiVU/668/edit

    In the case of a non-promise callback, you can create your own promise, and resolve that when appropriate.

    model: function(){
      var defer = Ember.RSVP.defer(),
          firebase = new Firebase('https://dawnbreaker.firebaseio.com'),
          fbLogin = new FirebaseSimpleLogin(firebase, this.authCompleted.bind(this));
    
    
      this.setProperties({
          defer: defer, 
          firebase: firebase,
          fbLogin: fbLogin
      });
    
      return defer.promise.then(function(result){
        // maybe redirect if authed etc...
      });
    },
    authCompleted: function(error, user){
      var defer = this.get('defer');
      //if authenticated
      defer.resolve({auth:true});      
      //else authenticated
      defer.resolve({auth:false});      
    }