Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Backbone.js Template and View security

Tags:

backbone.js

First, I apologize if this is a duplicate question. I have seen several questions that are similar but they didn't provide an answer that I could understand.

I have a Backbone.js Application, with lots of views/templates. Some views can be accesses only if the user logged in, some are not. I understand how to secure the underlying REST calls but I still don't understand what is the proper architecture/design for checking on specific views the actual access control level. Also, what is the right way of storing and validating a user after first login. For example :

  1. a user opens the first view, in which he can list products. This can be accessed without authentication. But if the user did, I need to add the data to the REST call. Because the response will be different(recommendation engine in the server side).
  2. same user, tries to access an 'add new product' template, in which he is supposed to be authenticated. I need to make sure he is, if not, redirect to a login page.

I have seen several solutions but all looks like hacks.

What is the proper, well designed, way of doing this ?

Thanks, Elad.

like image 855
user2502475 Avatar asked Jun 19 '13 18:06

user2502475


1 Answers

Assumptions on security

As you know, javascript is an interpreted language and thus it's source is human-readable, even if obfuscated. Thus, it is safe to assume that an evil or unregistered user, let's call him Edward, can have access to the whole client side code of the application. Furthermore, depending on your template loading mechanism, it is also a reasonable assumption that Edward can have full read access to your html templates.

Given the preceding assumptions, it is clear that what must be protected is the data populating these templates. Your REST API access points must enforce some authentication mechanism and return pertinent error messages in case of unauthorized access.

There are many tools/middleware/frameworks that implement good authentication schemes and most of them will use a session object to validate a request's status ( authorized, unauthorized ) but this is out of the scope of this question.

Dealing with unauthorized requests

Assuming you have an authentication mechanism that works on your REST API, here is how you could deal with unauthorized access.

Login required

For all the data that requires to be logged in, you can reserve an HTTP response code and navigate the application to the login view when that code is received. As you might have seen in other questions, you just have to set a callback for each error response corresponding to the 401 - unauthorized http code (assuming your backbone router is app.router):

$.ajaxError(function (event, xhr) {
  if (xhr.status == 401)
    app.router.navigate('/login', { trigger, true });
});

Now you just have to make sure that your REST API returns a 401 when this kind of request needs to redirect to the login form.

Branching cases

On more sensible cases, you might want to redirect user to a different view depending on it's status. On those cases, a data request to your REST API should not return a 401 as it would be intercepted by the former callback. Any other error code should do 403 - forbidden might be appropriate or 400 - bad request, it does not really matter.

That being said, when the user asks for a specific view from the backbone router, it is a good practice to get the data you need to properly show the view before you render it. When you will make a call for this specific data, if the user does not have the proper credentials to get it, your server will return a response with an error code that will not be intercepted by the application and you can then branch on it. An example:

// Assuming we are defining methods inside your backbone app router:

listProducts : function() {
  var sensibleData = new app.SensibleData(),
      products = new app.Products();
      router = this;
  products.fetch();
  sensibleData.fetch({
    success : function( model, response, options ) {
      router.listProductsSignedIn( model, products );
    },
    error : function( model, response, options ) {
      router.listProductsNotSignedIn( products );
    }
},

listProductsSignedIn : function( sensibleData, products ) {
  //Create and show the full products view with all data
},

listProductsNotSignedIn : function( products ) {
   //Create and show the limited products view with the products data
},
//...

Et Voilà, you are having a two steps router where the second step branches according to the user's credentials, the error being generated by a separate access point on your REST API for the sensible data that an unsigned user cannot access. Since the authorization process and enforcement is managed by the server, the only way for your app to know if the user has the right access is to try and fetch the data. The app can then react the right way.

Storing user information

One of the big advantages of having a one page javascript application is that you can keep states throughout the different views of the application. You might want to keep a user's information available somewhere in a state of your application in order to route the user properly and show the appropriate views. You might have some configuration options available for a user ( language, profile, etc... ). This information should be made available through a REST call and be the result of a successful login. Once the user has logged in, your app will have a defined user and can then use this status to display the right views.

If you don't share a global object for your app, a good way of having your different views share information is through the use of events.

// Assuming you have defined a User model that has a proper route for authentication.
var loginView = Backbone.View.extend({
  events : {'submit .login-form' : 'onLogin'},
  initialize : function() {
    this.model = new User();
    // here, we listen for the successful login event and we proxy it to the global
    // Backbone object in order to communicate with views that are out of scope.
    this.model.on('login:successful', function() { 
      Backbone.trigger('login:successful', this.model );
    });
  }
  onLogin : function() {
    var username = this.$('#username').val(),
        password = this.$('#password').val();
    this.model.authenticate({ 
      username : username, 
      password : password
    }); // this method should make a REST call and trigger an event on success
  }

Inside your backbone app router:

initialize : function() {
  // Here, we register to the successful login event and simply store the state of
  // a user inside of the router.
  this.listenTo( Backbone, 'login:successful', function( user ) {
    this.user = user;
  }, this );
}

You can now use this state for the whole time of the session and use it to branch in your routes.

// Still in the router
mainPage  : function() {
  if ( this.user ) {
    // show view for a registered user
  } else {
    // show a view for an unregistered user
  }
},
//...
like image 178
mor Avatar answered Oct 23 '22 10:10

mor