How to Implement User Roles in a Sencha Touch Application

In this tutorial I will show you how to create a Roles class that you can use to implement authentication and authorization in a Sencha Touch application.

sencha-touch-roles-class

This is a continuation of the authentication and authorization series we started with the How to Add a Login Screen to a Sencha Touch Application article, from which we will borrow the sample application that we will use in this tutorial.

Our demo application has a couple of views called Login and MainMenu. It also has a controller, which will handle the login operations and receive a server response containing the roles assigned to a given user.

Changing a View’s Behavior Based on a User’s Role

Let’s start by reviewing the views we created in the previous tutorial.

The Login view will acquire the user’s credentials and pass them to the controller so they can be sent to the server for authorization. Here’s its source code:

 Ext.define('MyApp.view.Login', {
    extend: 'Ext.form.Panel',
    alias: "widget.loginview",
    requires: ['Ext.form.FieldSet', 'Ext.form.Password', 'Ext.Label', 'Ext.Img', 'Ext.util.DelayedTask'],
    config: {
        title: 'Login',
        items: [
            {
                xtype: 'image',
                src: Ext.Viewport.getOrientation() == 'portrait' ? '../../../img/login.png' : '../../../img/login-small.png',
                style: Ext.Viewport.getOrientation() == 'portrait' ? 'width:80px;height:80px;margin:auto' : 'width:40px;height:40px;margin:auto'
            },
            {
                xtype: 'label',
                html: 'Login failed. Please enter the correct credentials.',
                itemId: 'signInFailedLabel',
                hidden: true,
                hideAnimation: 'fadeOut',
                showAnimation: 'fadeIn',
                style: 'color:#990000;margin:5px 0px;'
            },
            {
                xtype: 'fieldset',
                title: 'Login Example',
                items: [
                    {
                        xtype: 'textfield',
                        placeHolder: 'Username',
                        itemId: 'userNameTextField',
                        name: 'userNameTextField',
                        required: true
                    },
                    {
                        xtype: 'passwordfield',
                        placeHolder: 'Password',
                        itemId: 'passwordTextField',
                        name: 'passwordTextField',
                        required: true
                    }
                ]
            },
            {
                xtype: 'button',
                itemId: 'logInButton',
                ui: 'action',
                padding: '10px',
                text: 'Log In'
            }
         ],
        listeners: [{
            delegate: '#logInButton',
            event: 'tap',
            fn: 'onLogInButtonTap'
        }]
    },
    onLogInButtonTap: function () {

        var me = this,
            usernameField = me.down('#userNameTextField'),
            passwordField = me.down('#passwordTextField'),
            label = me.down('#signInFailedLabel'),
            username = usernameField.getValue(),
            password = passwordField.getValue();

        label.hide();

        // Using a delayed task in order to give the hide animation above
        // time to finish before executing the next steps.
        var task = Ext.create('Ext.util.DelayedTask', function () {

            label.setHtml('');

            me.fireEvent('signInCommand', me, username, password);

            usernameField.setValue('');
            passwordField.setValue('');
        });

        task.delay(500);

    },
    showSignInFailedMessage: function (message) {
        var label = this.down('#signInFailedLabel');
        label.setHtml(message);
        label.show();
    }
});

The MainMenu view is where the user is taken after she has been authenticated. Here’s its source code:

 Ext.define('MyApp.view.MainMenu', {
    extend: 'Ext.Panel',
    requires: ['Ext.TitleBar'],
    alias: 'widget.mainmenuview',
    config: {
        layout: {
            type: 'fit'
        },
        items: [{
            xtype: 'titlebar',
            title: 'Main Menu',
            docked: 'top',
            items: [
                {
                    xtype: 'button',
                    text: 'Log Off',
                    itemId: 'logOffButton',
                    align: 'right'
                }
            ]
        },
        {
            xtype: 'label',
            html: '',
            itemId: 'msgLabel',
            hidden: true,
            hideAnimation: 'fadeOut',
            showAnimation: 'fadeIn',
            style: 'margin:15px;'
        }],
        listeners: [{
            delegate: '#logOffButton',
            event: 'tap',
            fn: 'onLogOffButtonTap'
        }]
    },
    onLogOffButtonTap: function () {
        this.fireEvent('onSignOffCommand');
    }
});

MainMenu is an example of a view where you might want to display different content to different users, depending on their rights.

To demonstrate how this can be done, we will define a helper method that will show a custom message to the user. We will change the message based on the user’s rights.

Let’s call this method setMessage, and define it immediately after the onLogOffButtonTap method, like so:

setMessage: function (message) {
	var label = this.down('#msgLabel');
	label.setHtml(message);
	label.show();
}

Nothing fancy going on here as you can see. The method acquires a reference to the label in the view, and sets its html to the value of the message variable.

Now we can move on to the most interesting part, which is creating a class that will allow us to assign roles to the users of the application.

Creating a Sencha Touch Class to Define User Roles

Let’s define a Roles class as follows:

Ext.define('MyApp.model.Roles', {
	statics: {
		READER: 0x1,
		CONTRIBUTOR: 0x10,
		ADMINISTRATOR: 0x100,
		isReader: function (roles) {
			return ((roles & this.READER) === this.READER);
		},
		isContributor: function (roles) {
			return ((roles & this.CONTRIBUTOR) === this.CONTRIBUTOR);
		},
		isAdministrator: function (roles) {
			return ((roles & this.ADMINISTRATOR) === this.ADMINISTRATOR);
		}
	}
});

To keep things simple, we will use only three roles: READER, CONTRIBUTOR, and ADMINISTRATOR. Notice that we created the roles in the statics config of the class so we can use them without having to create a class instance. The same applies to the isReader, isContributor, and isAdministrator helper methods.

The values of the roles (0×1, 0×10, 0×100) can be combined to form a bits flag, giving us the ability to store multiple roles in a single value. This is helpful in scenarios where a user can play multiple roles at the same time. For example, we could define a hypothetical power user role like so:

MyApp.model.Roles.POWERUSER = MyApp.model.Roles.READER | MyApp.model.Roles.CONTRIBUTOR;

Defining the Controller

We will build the app’s controller based on the controller definition we used in the previous tutorial:

Ext.define('MyApp.controller.Login', {
	extend: 'Ext.app.Controller',
	config: {
		refs: {
			loginView: 'loginview',
			mainMenuView: 'mainmenuview'
		},
		control: {
			loginView: {
				signInCommand: 'onSignInCommand'
			},
			mainMenuView: {
				onSignOffCommand: 'onSignOffCommand'
			}
		}
	},

	// Session token
	sessionToken: null,

	// Transitions
	getSlideLeftTransition: function () {
		return { type: 'slide', direction: 'left' };
	},

	getSlideRightTransition: function () {
		return { type: 'slide', direction: 'right' };
	},

	onSignInCommand: function (view, username, password) {

		console.log('Username: ' + username + '\n' + 'Password: ' + password);

		var me = this,
			loginView = me.getLoginView();

		if (username.length === 0 || password.length === 0) {

			loginView.showSignInFailedMessage('Please enter your username and password.');
			return;
		}

		loginView.setMasked({
			xtype: 'loadmask',
			message: 'Signing In...'
		});

		Ext.Ajax.request({
			url: '../../services/auth.ashx',
			method: 'post',
			params: {
				user: username,
				pwd: password
			},
			success: function (response) {

				var loginResponse = Ext.JSON.decode(response.responseText);

				if (loginResponse.success === "true") {
					// The server will send a token that can be used throughout the app to confirm that the user is authenticated.
					me.sessionToken = loginResponse.sessionToken;
					me.signInSuccess();     //Just simulating success.
				} else {
					me.singInFailure(loginResponse.message);
				}
			},
			failure: function (response) {
				me.sessionToken = null;
				me.singInFailure('Login failed. Please try again later.');
			}
		});
	},

	signInSuccess: function () {
		console.log('Signed in.');
		var loginView = this.getLoginView(),
			mainMenuView = this.getMainMenuView();
		loginView.setMasked(false);

		var msg = 'You don\'t have any rights to use this app';

		mainMenuView.setMessage(msg);
		Ext.Viewport.animateActiveItem(mainMenuView, this.getSlideLeftTransition());
	},

	singInFailure: function (message) {
		var loginView = this.getLoginView();
		loginView.showSignInFailedMessage(message);
		loginView.setMasked(false);
	},

	onSignOffCommand: function () {

		var me = this;

		Ext.Ajax.request({
			url: '../../services/logoff.ashx',
			method: 'post',
			params: {
				sessionToken: me.sessionToken
			},
			success: function (response) {

				// TODO: Implementation.
			},
			failure: function (response) {

				// TODO: Implementation.
			}
		});

		Ext.Viewport.animateActiveItem(this.getLoginView(), this.getSlideRightTransition());
	}
});

This controller keeps references to the two application views, and defines events and methods to handle sign-in and sign-off operations.

But we also need it to handle user roles, so let’s create a private userRoles variable to store the user’s roles returned by the server:

sessionToken: null,
userRoles: null,

Reading User Roles Sent by the Server

Now we will modify the onSignInCommandMethod so it loads the roles information sent by the server into the userRoles variable:

onSignInCommand: function (view, username, password) {

	console.log('Username: ' + username + '\n' + 'Password: ' + password);

	var me = this,
		loginView = me.getLoginView();

	if (username.length === 0 || password.length === 0) {

		loginView.showSignInFailedMessage('Please enter your username and password.');
		return;
	}

	loginView.setMasked({
		xtype: 'loadmask',
		message: 'Signing In...'
	});

	Ext.Ajax.request({
		url: '../../services/auth.ashx',
		method: 'post',
		params: {
			user: username,
			pwd: password
		},
		success: function (response) {

			var loginResponse = Ext.JSON.decode(response.responseText);

			if (loginResponse.success === "true") {
				// The server will send a token that can be used throughout the app to confirm that the user is authenticated.
				me.sessionToken = loginResponse.sessionToken;
				me.userRoles = parseInt(loginResponse.roles);
				me.signInSuccess();     //Just simulating success.
			} else {
				me.singInFailure(loginResponse.message);
			}
		},
		failure: function (response) {
			me.sessionToken = null;
			me.singInFailure('Login failed. Please try again later.');
		}
	});
}

Notice how the method is expecting the server to send the user’s roles in the form of a property of the loginResponse object:

me.userRoles = parseInt(loginResponse.roles);

Now we can do something with the roles information we just received. For example, we can change the singInSuccess method like so:

signInSuccess: function () {
	console.log('Signed in.');
	var loginView = this.getLoginView(),
		mainMenuView = this.getMainMenuView();
	loginView.setMasked(false);

	var msg = 'You don\'t have any rights to use this app';

	if (MyApp.model.Roles.isReader(this.userRoles)) {
		msg = 'Welcome. You have READER rights.';
	}
	if (MyApp.model.Roles.isContributor(this.userRoles)) {
		msg = 'Welcome. You have CONTRIBUTOR rights.';
	}
	if (MyApp.model.Roles.isAdministrator(this.userRoles)) {
		msg = 'Welcome. You are an administrator.';
	}
	mainMenuView.setMessage(msg);
	Ext.Viewport.animateActiveItem(mainMenuView, this.getSlideLeftTransition());
}

Within signInSuccess, we use the isReader, isContributor, and isAdministrator methods defined in the Roles class to modify the value loaded into the msg variable. Then, we invoke the MainView’s setMessage method to render the message inside the view.

Conclusion and Next Steps

There you have it. You can use the approach demonstrated by this tutorial in your own Sencha Touch apps to read user roles from the server and modify the state or behavior of the app.

In the next tutorial on this subject we will go over a more complex example that will show you how to control the creation of views in your Sencha Touch application based on the user’s roles.

Stay tuned!

Comments

  1. Dawesi says

    Great tutorials here bro!

    In your example the model should be ‘Role’ as models are singular and stores are plural using sencha’s naming conventions.

    As a dev it’s also easier to quickly understand what object you are using by this convention (as i never read the dot folders at a quick glance)

    • says

      “Roles” in this example is similar to an enumeration. I don’t know if you are familiar with them. I’m modeling the roles a user can play, not a role. Therefore, the convention does not apply. Or if you want to say that it applies, it’s safe to ignorig.

  2. Lucas says

    Great tutorial.
    Question, how is this truly secure? Would your web host (IIS/Tomcat) require authentication for your app? or if you are just calling a service with a username/password payload, how is that secured? encryption of some sort?
    Thanks!

    • says

      Nothing on the client side is secure. This is one of the problems with web applications. You have to encrypt your traffic with the server and validate everything sent from the client so you can clearly establish that the user is authentic and has the appropriate rights.

  3. Jeffrey says

    Jorge, this is simply awesome! I have been looking for a comprehensive – but – concise tutorial on mobile (Sencha) development. Great job!!

    Just curious — have you done any Sencha development where the backend database is say – Oracle or MS SQL Server?

    • says

      Thank you. I have done a few projects using Sencha Touch and a SQL Server backend. And I am currently reviewing a Sencha Touch project with .NET backend that connects to Oracle.

Leave a Reply

Your email address will not be published. Required fields are marked *