At Ortus Solutions, we love the holidays, and we wanted to gift you a gift of developer productivity, we will share a few tips and tricks that will keep giving all year around. In this series we'll be giving you 12 ContentBox tips. Keep your eye out for other 12 tips of Christmas series on our blog, including a new one this year, 12 modules of Christmas on ForgeBox.

Day 10 - Security and Permissions in ContentBox Modules - One of the reasons I love working with ContentBox is all of the built in User management, permissions, roles, and creating ColdBox modules to extend ContentBox is easy. Today we'll look at how to use Security and Permissions in your ContentBox modules.

If your Module is using the ContentBox Module lifecycle, ContentBox preps your request, and handles tasks like themes, settings, checking for a logged in user etc. The current logged in user, is always available to you in the prc. This is handled by an interceptor, to ensure it is done on every request. Whether you are using a front end or admin module, you can access that user with `prc.oCurrentAuthor`

A great addition to your ContentBox Module ( also works with straight ColdBox apps ) is a module designed by Eric Peterson, called CB Guard. https://github.com/coldbox-modules/cbguard

This gives us a simple but powerful convention based security approach. There is a lot of great information on using this module in the readme on github, but we'll give you a run down here, and some secrets to using it with ContentBox.

To ensure a user is logged in to access a handler, you can add the metadata `secured` to your handler. This locks down the entire handler

component secured {

    function index( event, rc, prc ) {
        // ...
    }

    function show( event, rc, prc ) {
        // ...
    }

}

To ensure a user is logged in to access an action, you can add the metadata `secured` to your action.

component {

    function create( event, rc, prc ) secured {
        // ...
    }

}

To ensure a user is logged in and has the correct permission to access a handler, you can add the metadata `secured=”list,of,permissions”` to your handler. The user must have one of the listed permissions.

component secured="admin" {
	
    function index( event, rc, prc ) {
        // ...
    }

    function show( event, rc, prc ) {
        // ...
    }

}

To ensure a user is logged in and has the correct permission to access an action, you can add the metadata `secured=”list,of,permissions”` to your action. The user must have one of the listed permissions.

component {
	
    function show( event, rc, prc ) secured="admin,reviews_posts" {
        // ...
    }

}

These two approaches can be combined and both handler and actions can be secured together:
While the user needs to be logged in to interact at all with this handler, they also need the create_posts permission to interact with the new action.

component secured {

    function index( event, rc, prc ) {
        // ...
    }

    function new( event, rc, prc ) secured="create_posts" {
        // ...
    }

}

Configuration

CBGuard can be used with any ColdBox app, all you have to do is configure it. You can set settings for redirects:

  • authenticationOverrideEvent 
  • authorizationOverrideEvent 
  • authenticationAjaxOverrideEvent 
  • authorizationAjaxOverrideEvent 

As well as the redirects, you have to define how CBGuard checks your user is authenticated, and if the authenticated user is authorized ( has permission ), by defining the CFC that conforms to AuthenticationServiceInterface and HasPermissionInterface.

To configure the AuthenticationService, set the value of authenticationService in your moduleSettings to a WireBox mapping:

moduleSettings = {
    cbguard = {
        authenticationService = "SecurityService@myapp"
    }
};

The default authenticationService for cbguard is AuthenticationService@cbauth. cbauth follows the AuthenticationServiceInterface out of the box.

Advanced Setup - Overriding the Interface to work with ContentBox

You can change the method names called on the AuthenticationService and the returned User if you need to. We highly discourage this use case, as it makes it harder to utilize the cbguard conventions across projects. However, should the need arise, you can modify the method names as follows:

moduleSettings = {
    cbguard = {
        methodNames = {
            isLoggedIn    = "getIsLoggedIn",
            getUser       = "retrieveUser",
            hasPermission = "checkPermission"
        }
    }
};

Using CBGuard in a ContentBox App

Here is an example config for a real ContentBox app using CBGaurd, just add this into you /config/ColdBox.cfc

moduleSettings = {
	cbguard = {
		authenticationService = "UserService@rccore",
		methodNames = {
			isLoggedIn    = "isLoggedIn",
			getUser       = "getAuthorSession",
			hasPermission = "checkPermission"
		},
		authenticationOverrideEvent			= "security:login.new",
		authorizationOverrideEvent			= "ui:auth.onAuthorizationFailure",
		authenticationAjaxOverrideEvent		= "security:login.new",
		authorizationAjaxOverrideEvent		= "ui:auth.onAuthorizationFailure"
	}
};

What are we doing in this config?

  • We are using a custom authenticationService CFC, we'll share below
  • We are changing method names, so they match ContentBox's normal methods for checking Permission, and checking if a user is logged in for example.
  • We set authentication ( not logged in error ) redirects to the security module, with the action login.new.
  • We set authorization ( logged in but not the right permission ) redirects to the UI module, with the action auth.onAuthorizationFailure.

UserService.cfc required for CBGuard with ContentBox

Other than this configuration, the only code changes we need to implement, are in the UserService, lets look at that UserService.cfc.
We are looking at merging these requirements of CBGuard to make this even easier, but for now, you need to create a CFC like so.

/**
* User Service
* This User Service extends the ContentBox Author Service, allowing us to override and extend ContentBox's author service for the needs of this application
*/
component singleton extends="contentbox.models.security.AuthorService" {
	

	/**
	* Constructor
	*/
	public UserService function init(){
		super.init();
		return this;
	}

	/**
	* Proxy the getAuthorSession to the ContentBox security service getAuthorSession()
	*
	* @return author entity of the logged in user, or an empty author entity
	*/
	function getAuthorSession(){
		return securityService.getAuthorSession();
	}

	/**
	* Determinies if there is currently a user logged in
	*
	* @return boolean value - if there is a user logged in, or not.
	*/
	function isLoggedIn(){
		var author = getAuthorSession();
		return ( author.isLoaded() && author.isLoggedIn() );
	}
}

With some simple meta data, a little config, and this simple CFC, you can now use authentication and authorization all over your ContentBox modules.
Try it out, and let us know what you think.

Happy Holidays