An Authentication directive

On the server side is absolutely normal to decorate your Controllers or Actions with the Authorization attribute in order to manage correctly permissions and access to the resources/routes.

Sometime the problem is to manage the permissions or the roles in a much more atomic way in the views.
The scenario that a friend described to me was: I want in the a page to show/hide or enable/disable parts of the it depending on the roles of the user.

Aside the normal server side controls he needed to manage easily the roles in the view to improve the user experience without to put a lot of conditions using razor.

There is a lot of way to manage the problem, but I thought that to create a specific directive was a very clean way to deal with it.
If you have a SPA it is tricky to use razor or in any case you don’t want to parse the templates server side, so this approach works even in that scenario.

How the directive works:

<input type="text"
   is-authorized
   for-roles="arrayOfRolesAuthorizedToUseThisComponent"
   your-roles="arrayOfTheRolesOfTheUser" />

If the user has at least one of the roles allowed in the for-roles array the input won’t change, in the other case it will be disabled.

Depending on the tag in whom you put the directive the behaviour change:

All input are disabled
All button are disabled
Any other tag and its content will be removed

To simplify the work with huge forms you can use the directive on the form tag and in that case all the form controls inside it will be disabled following the rules above.

Let’s put few test


describe('IsAuthorized: ', function () {
	var rootScope, compile, target, scope;

	beforeEach(function () {
		module('maga-library');
		inject(function ($compile, _$rootScope_) {
			compile = $compile;
			rootScope = _$rootScope_;

		});
	});

	describe('When a button has the directive', function () {

		it('it should be disabled if the user is not authorized', function () {
			scope = rootScope.$new();
			scope.roles = ['user'];
			scope.yourRoles = ['admin'];

			target = compile('<button is-authorized for-roles="roles" your-roles="yourRoles"></button>')(scope);
			scope.$digest();

			expect($(target).attr('disabled')).toBeTruthy();
		});

		it('it should be enabled if the user is authorized', function () {
			scope = rootScope.$new();
			scope.roles = ['admin'];
			scope.yourRoles = ['admin'];

			target = compile('>button is-authorized for-roles="roles" your-roles="yourRoles"></button>')(scope);
			scope.$digest();

			expect($(target).attr('disabled')).toBeFalsy();
		});

	});

	describe('When a input has the directive', function () {

		it('it should be readonly if the user is not authorized', function () {
			scope = rootScope.$new();
			scope.roles = ['user'];
			scope.yourRoles = ['admin'];

			target = compile('<input is-authorized for-roles="roles" your-roles="yourRoles" />')(scope);
			scope.$digest();

			expect($(target).attr('disabled')).toBeTruthy();
		});

		it('it shouldnt be readonly if the user is authorized', function () {
			scope = rootScope.$new();
			scope.roles = ['admin'];
			scope.yourRoles = ['admin'];

			target = compile('<input is-authorized for-roles="roles" your-roles="yourRoles" />')(scope);
			scope.$digest();

			expect($(target).attr('disabled')).toBeFalsy();
		});

	});

	describe('When a textarea has the directive', function () {

		it('it should be readonly if the user is authorized', function () {
			scope = rootScope.$new();
			scope.roles = ['user'];
			scope.yourRoles = ['admin'];

			target = compile('<textarea is-authorized for-roles="roles" your-roles="yourRoles"></textarea>')(scope);
			scope.$digest();

			expect($(target).attr('disabled')).toBeTruthy();
		});

		it('it shouldnt be readonly if the user is authorized', function () {
			scope = rootScope.$new();
			scope.roles = ['admin'];
			scope.yourRoles = ['admin'];

			target = compile('<textarea is-authorized for-roles="roles" your-roles="yourRoles"></textarea>')(scope);
			scope.$digest();

			expect($(target).attr('disabled')).toBeFalsy();
		});

	});

	describe('When a form has the directive', function () {

		it('it should be readonly every input and textarea and disabled every button if the user is not authorized', function () {
			scope = rootScope.$new();
			scope.roles = ['user'];
			scope.yourRoles = ['admin'];

			target = compile('<form is-authorized for-roles="roles" your-roles="yourRoles"><textarea class="textArea"></textarea><input type="text" class="input" /><button class="button"></button></form>')(scope);
			scope.$digest();

			expect($(target).find('.textArea').attr('disabled')).toBeTruthy();
			expect($(target).find('.input').attr('disabled')).toBeTruthy();
			expect($(target).find('.button').attr('disabled')).toBeTruthy();
		});

		it('it shouldnt be readonly every input and textarea and disabled every button if the user is authorized', function () {
			scope = rootScope.$new();
			scope.roles = ['admin'];
			scope.yourRoles = ['admin'];

			target = compile('<form is-authorized for-roles="roles" your-roles="yourRoles"><textarea class="textArea"></textarea><input type="text" class="input" /><button class="button"></button></form>')(scope);
			scope.$digest();

			expect($(target).find('.textArea').attr('disabled')).toBeFalsy();
			expect($(target).find('.input').attr('disabled')).toBeFalsy();
			expect($(target).find('.button').attr('disabled')).toBeFalsy();
		});

	});

	describe('When another tag has the directive', function () {

		it('it should be removed if the user is not authorized', function () {
			scope = rootScope.$new();
			scope.roles = ['user'];
			scope.yourRoles = ['admin'];

			target = compile('<div id="parent"><div class="child" is-authorized for-roles="roles" your-roles="yourRoles"></div></div>')(scope);
			scope.$digest();
			expect(target.find('.child').length).toBe(0);
		});

		it('it shouldnt be removed if the user is not authorized', function () {
			scope = rootScope.$new();
			scope.roles = ['admin'];
			scope.yourRoles = ['admin'];

			target = compile('<div id="parent"><div class="child" is-authorized for-roles="roles" your-roles="yourRoles"></div></div>')(scope);
			scope.$digest();

			expect(target.find('.child').length).toBe(1);
		});

	});

	describe('When there is the directive on a tag', function () {

		it('it should be possible specify multiple roles to be granted access', function () {
			scope = rootScope.$new();
			scope.roles = ['user', 'admin'];
			scope.yourRoles = ['admin'];

			target = compile('<div id="parent"><div class="child" is-authorized for-roles="roles" your-roles="yourRoles"></div></div>')(scope);
			scope.$digest();
			expect(target.find('.child').length).toBe(1);
		});

	});

});

Try it on jsfiddle here
The solution on github

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s