sencha-logoThings have been going great. We’ve been plugging along in our app, making cool input forms and more or less getting to a place where we felt we had something that works. And then the bombshell.

One of the business users asked about the login form. Login form? Our impression up until this time was that this app was being developed for one person to use. And because we were going to keep this behind our internal network firewall, there really wasn’t any need for user authentication, session management, etc.

Well, so much for that assumption. Now not only do we have to provide some form of user authentication, the higher-ups also want to restrict access within the app itself to certain parts, so that only certain users can access certain sections.

We freeze for a moment, and feel that strange feeling of panic start to arise. But then we realize: oh yeah, we’ve been developing an ExtJS application. All of this will be easy, and will only require the most minor of refactoring.

Content in this thought, we return blissfully to dungeon raids in Cube World.

NOTE: The code for this session can be found on GitHub.

User Authentication and Roles

While the story above is probably a bit ridiculous, things like this happen ALL the time in the real world of development. We make assumptions about particular things, and follow certain paths based on those assumptions. And then, out of the blue, requirements come down that throw wrenches into our plans. As unavoidable as these changes are, the beauty of ExtJS 4 is that because we are designing in an event-driven manner, we have very loose coupling within our application. Within each particular section of our app, the dependencies are few, and the ones that exist are themselves loosely related. This translates into an environment that is very amenable to unexpected changes; we can refactor with ease and not have to completely rewrite our application.

So given that introduction, in this installment, we’re going to attack the challenge laid out by our hypothetical scenario. In doing so, we need to accomplish the following:

  • Create user authentication support
  • Create login form
  • Create logout action
  • Create user roles
  • Change the “view” of the application, based on the authenticated user’s assigned roles

No need to discuss it further…let’s go!

In what follows, there will be a number of moving parts that have to occur at correct times, chronologically, as the development occurs. If you get stuck, feel free to skip to the end, get things working, and then reverse-engineer the steps that got us from soup to nuts.

Role Management

The easiest place to begin is with knocking out the the Role management. This can be just like the other Option management sections we’ve already built, so this should be old hat by now.

Once this is done, the next thing we need to do is to update the Staff table with a few new fields:

  • Username: The username to be used by the staff member when they logon
  • Password: Um, a password
  • Roles: One-to-Many relationship, wherein the staff member can occupy multiple roles at the same time (I use a link table for this)

We also want to upgrade our Staff management form with these form fields, one for adding/altering the Username, and another to allow for the selection of the Staff person’s Roles (I suggest an Ext.ux.form.field.ItemSelector for this one…). In the end, we need it to look something like this:

StaffWRoles

 

Given what we’ve already developed in this app, you should be comfortable with pulling this off, so I’ll leave you to it.

There’s a very decent chance that for your application, you’d want to use a different table than the equivalent of “Staff” in this demo application. While we’ll be using Staff in the examples that follow, there’s absolutely no reason why you couldn’t integrate the same logic we’ll be implementing for a different table, or even a different User management system altogether (like Active Directory)

A Little Strategy

If you remember way back to our first session, we configured our app.js file with the magic autoCreateViewport config set to true. This told ExtJS to go ahead and load and instantiate the Viewport immediately, even before our application’s launch() method. Well, now that we have to handle both logging in AND role-based views, we can’t get away with this anymore. After all, we don’t want to show the not-logged-in user our application, and then switch the view up on them once they login. No, we’d prefer to not render the Viewport until after we’ve confirmed that they are authenticated users.

To pull this off, there are a few things we need to do.

First, let’s remove autoCreateViewport config altogether (it’s false by default).

Next, since we’re not delegating instantiation of the Viewport to convention, we need to take care of it ourselves. At this point in time, we’ll not actually worry about the authentication/roles stuff. Let’s first just concentrate on getting our Viewport back :)

As with everything ExtJS, there are a number of ways we could do this. However, since we already have our fancy App.js controller (which we said would be used for high-level, global application stuff), it might make sense to put it there.

So let’s make a new method in App.js called setupApplication():

/**
 * Entry point for our application. Will render the viewport, and do any other setup required for our application
 */
setupApplication: function() {
    var me = this;
    // create the viewport, effectively creating the view for our application
    Ext.create( 'CarTracker.view.Viewport' );
     // init Ext.util.History on app launch; if there is a hash in the url,
    // our controller will load the appropriate content
    Ext.util.History.init(function(){
        var hash = document.location.hash;
        me.fireEvent( 'tokenchange', hash.replace( '#', '' ) );
    })
    // add change handler for Ext.util.History; when a change in the token
    // occurs, this will fire our controller's event to load the appropriate content
    Ext.util.History.on( 'change', function( token ){
        me.fireEvent( 'tokenchange', token );
    });
}

Nothing crazy here. Besides moving our History stuff from app.js into this method, the only other thing that setupApplication() does is to instantiate our Viewport. Big whoop :)

To wrap this up, let’s add a global event listener to App.js:

init: function() {
    this.listen({
        controller: {
            '#App': {
                tokenchange: this.dispatch
            }
        },
        component: {
            'menu[xtype=layout.menu] menuitem': {
                click: this.addHistory
            } 
        },
        global: {
            beforeviewportrender: this.setupApplication
        },
        store: {},
        proxy: {
            '#baserest': {
                requestcomplete: this.handleRESTResponse
            }
        } 
    });
}

And finally, in app.js, let’s fire the event in the launch() method:

launch: function( args ) {
    // "this" = Ext.app.Application
    var me = this;
    Ext.globalEvents.fireEvent( 'beforeviewportrender' );        
}

If we reload our application in the browser, everything should appear as it always had before. The only difference is that we’ve taken back explicit control of instantiating the Viewport. While this is a relatively simple thing to do, it is powerful–now we can choose precisely when the Viewport is rendered, and can do anything that we would like to do before that happens…like logging in. And, of course, because we’re using an event to inform App.js, if our needs in the application ever change, we can easily register different events to change how this process occurs.

Security Controller

Even though our security needs at the moment are relatively small, we’ll go ahead and package them together to future proof our app a little bit. So let’s go ahead and create a new Security.js controller, as well as a Form view and Window view to handle the form. You should know the routine by now :)

Ok, since we’ve refactored how we initialize our app, we can now display the login form when our app first loads. So for now, let’s go ahead and tap into the same beforeviewportrender event in our Security.js, and disable it in App.js. We’ll switch things up a bit later, but for now we only really care about the login form:

/**
 * Controller for all security functionality
 */
Ext.define('CarTracker.controller.Security', {
    ...
    init: function() {
        this.listen({
            ...
            global: {
                beforeviewportrender: this.processLoggedIn
            },
            ...
        });
    },
    /**
     * Main method process security check
     */
    processLoggedIn: function() {
        var me = this;
        // make remote request to check session
        Ext.Ajax.request({
            url: '/security/checklogin',
            success: function( response, options ) {
                // decode response
                var result = Ext.decode( response.responseText );
                // check if success flag is true
                if( result.success ) {
                    // has session...add to application stack
                    CarTracker.LoggedInUser = Ext.create( 'CarTracker.model.security.User', result.data );
                    // fire global event aftervalidateloggedin
                    Ext.globalEvents.fireEvent( 'aftervalidateloggedin' );
                } 
                // couldn't login...show error
                else {
                    Ext.widget( 'security.login.window' ).show();
                }
            },
            failure: function( response, options ) {
                Ext.Msg.alert( 'Attention', 'Sorry, an error occurred during your request. Please try again.' );
            }
        })
    }
});

So what we’re doing here is that before anything else in our app occurs, the very first thing we want to do is to call this new processLoggedIn() method. In this method, we need to do a few things:

  • Check if user has a session (e.g., is logged in)
  • IF logged in, continue loading the application and set details about the logged in user into the application
  • IF NOT logged in, show login form

In our example, we’re asking the server about the nature of the user’s session. If a session exists, we create an instance of a new model (security.User), and set it into our application.

/**
 * {@link Ext.data.Model} for Security User
 */
Ext.define('CarTracker.model.security.User', {
    extend: 'Ext.data.Model',
    fields: [
        // non-relational properties
        {
            name: 'FirstName',
            type: 'string',
            persist: false
        },
        {
            name: 'LastName',
            type: 'string',
            persist: false
        },
        {
            name: 'Username',
            type: 'string',
            persist: false
        },
        {
            name: 'Roles',
            type: 'any',
            persist: false
        }
    ],
    inRole: function( RoleID ) {
        var me = this;
        return Ext.Array.contains( me.get( 'Roles' ), RoleID );
    } 
});

We do this because later on, we’ll want to interrogate this security.User object to determine if the logged in user is in a particular role, and then use this information to decide what sections of the site to show or hide (more on this later).

Once this is done, we fire another event which our App.js can then use to know to instantiate the Viewport.

If the user is not logged in, however, we’ll merely display the login form, which might look something like this:

LoginForm

Of course, we’ve already wired up a number of buttons to controllers to handle form submissions, so I’ll leave that you. Inevitably, though, we want to call a method (doLogin(), in this case) that will process the login attempt:

/**
 * Handles form submission for login
 * @param {Ext.button.Button} button
 * @param {Ext.EventObject} e
 * @param {Object} eOpts
 */
 doLogin: function( button, e, eOpts ) {
    var me = this,
        win = button.up( 'window' ),
        form = win.down( 'form' ),
        values = form.getValues(),
        hashedPassword;
    // simple validation
    if( Ext.isEmpty( values.Username ) || Ext.isEmpty( values.Password ) ) {
        Ext.Msg.alert( 'Attention', 'Please complete the login form!' );
        return false;
    }
    Ext.Ajax.request({
        url: '/security/login',
        params: {
            Username: values.Username,
            Password: CarTracker.security.crypto.SHA1.hash( values.Password )
        },
        success: function( response, options ) {
            // decode response
            var result = Ext.decode( response.responseText );
            // check if success flag is true
            if( result.success ) {
                // has session...add to application stack
                CarTracker.LoggedInUser = Ext.create( 'CarTracker.model.security.User', result.data );
                // fire global event aftervalidateloggedin
                Ext.globalEvents.fireEvent( 'aftervalidateloggedin' );
                // show message
                Ext.Msg.alert( 'Attention', 'You successfully logged in. Welcome to Car Tracker!!' );
                // close window
                win.close();
            } 
            // couldn't login...show error
            else {
                Ext.Msg.alert( 'Attention', result.message );
            }
        },
        failure: function( response, options ) {
            Ext.Msg.alert( 'Attention', 'Sorry, an error occurred during your request. Please try again.' );
        }
    });
 }

Nothing too terribly interesting here. Some simple validation, and an AJAX request to attempt the login…all stuff we’ve done before. If the login is successful, we pick up where we left off in processLoggedIn(), firing the same event which can be intercepted by App.js to initialize the Viewport. Same kind of stuff we’ve been doing for the last several sessions, just different names. No biggie.

NOTE: You’ll notice that I’m using CarTracker.security.crypto.SHA1 to hash the password being sent in the AJAX request. While we’re not going to cover that in this session, feel free to check the source to see how this works. It is a pretty straight port of a similar class which can be found here.

Finally, while we’re at it, we might as well implement a logout method of some kind. We’ll add a logout button to our main navigation menu, so we can pre-emptively put this in place, knowing what parameters to expect from the button click action:

/**
 * Handles logout
 * @param {Ext.button.Button} button
 * @param {Ext.EventObject} e
 * @param {Object} eOpts
 */
 doLogout: function( button, e, eOpts ) {
    var me = this;
    Ext.Msg.confirm( 'Attention', 'Are you sure you want to logout of Car Tracker?', function( button ) {
        if( button=='yes' ) {
            Ext.Ajax.request({
                url: '/security/logout',
                method: 'GET',
                success: function( response, options ) {
                    window.location.reload( true );
                },
                failure: function( response, options ) {
                    Ext.Msg.alert( 'Attention', 'Sorry, an error occurred during your request. Please try again.' );
                }
            });                
        }
    });
 }

So now that we have the entire login cycle complete, the only thing left to do is update our App.js controller. Before, we had used the beforeviewportrender event to trigger the initialization of our Viewport. However, since we’ve used that for the pre-handling of security concerns, let’s change it to the new event we’ve fired from our Security.js controller: aftervalidateloggedin. If we now reload the browser and login, we should see our app load exactly as it had before. Perfecto!

NOTE: If you’re not already convinced, I hope this illustrates the power of event-driven development in ExtJS 4 applications. By specifying that particular parts of the application function based on certain events, it is incredibly easy to shuffle things around without much headache whatsoever. Heck, in the preceeding example, we fundamentally changed how our application starts up, and yet the pain required to make this change was precisely zero. We didn’t have refactor a bunch of files or hack anything together. Rather, we just leveraged the framework according to its strengths, and it really paid off. Awesomeness.

Handling Roles

Now that the requirement of a user login is satisfied, the last thing we need to address is the challenge of how to dynamically alter the “view” of our application based on the logged-in user’s assigned roles.

For our needs, we want the following:

Admin Role

  • Can see and do everything

Content Manager

  • Can only see “Inventory” section
  • Can view and edit Car records (but cannot delete)
  • Cannot enter any “sales” information when creating/editing Car records (fields should be disabled, but visible)

There are a few ways to do this.

One way would be to defer the production of component configuration until requested, and generate it based on the user’s role. For example, since we can create components based on metadata returned from the server, we could generate the structure of our Menu, for example, from an AJAX request, instead of having a hard-coded version of it as a class in our application. Or when the user chooses to edit a Car record, we could return the form configuration from the server based on the user’s role.

Another way we could do it is to keep the configuration the way we have it, and simply show/hide particular elements of our application based on the logged in user’s roles. For brevity’s sake, this is the option we’ll use for this application.

NOTE: The proper option to choose depends very much on the needs of your application, and the viability of simply “hiding” particular aspects of your application vs. not generating it in the first place. If you have a potentially malicious user base, the later might be a better option, since you won’t be serving up the full app in the source for them to see and manipulate at will. Alternatively, the former option (the one we’ll be using below) does not require you to extend ExtJS requirements to the server, allowing you to maintain a single repository of ExtJS-related code, not multiple.

As a simple example, let’s start with the first requirement: if the logged in user only belongs to the Content Manager role, they should only be able to see the Inventory menu item. Therefore, we need to hide the others. Here’s one way to do that:

Menu.js

/**
 * Main top-level navigation for application
 */
Ext.define('CarTracker.view.layout.Menu', {
    extend: 'Ext.menu.Menu',
    ...
    initComponent: function(){
        var me = this;
        Ext.applyIf(me,{
            items: [
                {
                    text: 'Options',
                    iconCls: 'icon_gear',
                    hidden: !CarTracker.LoggedInUser.inRole( 1 ),
                    ...
                },
                {
                    xtype: 'menuseparator',
                    hidden: !CarTracker.LoggedInUser.inRole( 1 )
                },
                {
                    text: 'Sales Staff',
                    itemId: 'staff',
                    iconCls: 'icon_user',
                    hidden: !CarTracker.LoggedInUser.inRole( 1 )
                },
                {
                    xtype: 'menuseparator',
                    hidden: !CarTracker.LoggedInUser.inRole( 1 )
                },
                {
                    text: 'Inventory',
                    itemId: 'inventory',
                    iconCls: 'icon_tag'
                },
                {
                    xtype: 'menuseparator'
                },
                {
                    text: 'Logout',
                    itemId: 'logout',
                    iconCls: 'icon_login'
                }
            ]
        });
        me.callParent( arguments );
    } 
});

If you remember, when logging in, we set the LoggedInUser model in our CarTracker application. Since it’s now globally available, we can simply call the custom method inRole() on the model, and return whether or not the logged in user belongs to the requested role. In the example above, if the user doesn’t belong to the role (1–which is Admin), we set the hidden flag of the component.

That’s it, pretty simple. So now you can go ahead and go to other components and views, and use this to fulfill the remainder of the requirements for role management.

NOTE: In the Menu example, we could have also approached this by creating the array of menu items outside of the items config, only adding items to the array that match the user’s role. Either way gets the same functional result, so it’s up to you if you want to go that extra step.

Wrapping Up

That wasn’t too bad, was it? Even though we were thrown a curveball by our managers, our good practices in building our ExtJS 4 app thus far made it super-easy to implement an entirely new section of unplanned functionality. With this out of the way, we can finally get back to the reports and charts that we had planned on earlier. Hooray!

Wrapping Up…More

Even though we briefly touched on security and logging in during this installment, I don’t want to give the impression that what we’ve done in any way constitutes a “secure” application. All that we *really* did was to provide a way within our application to allow users to login, and provided a minimal safeguard of hashing the submitted password before it is transmitted over the wire. To really secure an ExtJS 4 application (or any other JS application, for that matter), we definitely need to ensure that the protections setup on our server stack are sufficient for our security needs. These concerns are outside the scope of this series, so if you need a *secure* JavaScript application, be sure to investigate all the issues related to this subject thoroughly before going live.

Remember, your JS application is able to be viewed by literally anyone who wants to see it (assuming it’s publicly accessible), and it’s rather trivial to de-bfuscate your code into something readable. Be sure that what is in your app is what you are comfortable with the world seeing, and that you have adequate protections in place server-side for the inevitable attacks that will come as a result of your code being public.