Introduction

In this post I will guide you through setting up cbSecurity with the flexible cbAuth validator and annotation based security. Before we start let’s look at the basics, as described in Getting Started | Overview at https://coldbox-security.ortusbooks.com.

When you install and configure the cbsecurity module it will wrap itself around the preProcess interception point. This point happens before any event execution in coldbox and thus is the perfect point to inspect incoming requests. The cbsecurity interceptor will try to validate your request against a configured validator. The validator will tell back if you are allowed access, and if not , what kind of validation is broken: authentication or authorization.

  • Authentication is when a user is not logged in
  • Authorization is when a user does not have the right permissions to access an event handler or event action

The validator checks if a request needs authentication and authorization and returns a struct with this information based on a list of rules and/or annotations. In this post I will focus on annotations, because they are a lot easier to understand and often it’s all you need. cbSecurity has three different types of Security validator: CFML security validator, CBAuth validator and custom validator. In this post we focus on the CBAuth validator: it is the default in cbsecurity, it is very flexible, has a nice API, and you don’t have to create your own validator.

CBAuth

Ok, so let’s get started with this cbsecured demo app. Let’s fire up a commandbox window and create a app called cbsecurity_demo :

coldbox create app cbsecurity_demo

Install cbsecurity:

install cbsecurity

When installing cbsecurity, cbauth will also be installed as a dependency. Now you have to configure both cbsecurity AND cbAuth in the module. But first I’ll explain what cbauth is.

CbAuth is about NOT reinventing the weel each time you have to code some authentication schema. And by providing a standard API you can swap out authentication schemes, because they all conform to the same API. Cbauth has an authenticationservice with the following methods

  • login( User )
  • logout( )
  • authenticate( username, password ) will return a loggedIn user
  • isLoggedIn( )
  • check( ) alias for isLoggedIn( )
  • guest( ) returns the reverse of isLoggedIn()
  • getUser() returns the loggedIn user
  • user() alias for loggedIn User
  • getUserId()

These methods can be accessed by injecting your authentication service with wirebox

property name="auth" inject="authenticationService@cbauth";
// OR
var auth = wirebox.getInstance( "authenticationService@cbauth" );

or using the helper method cbauth()which will also give you a reference to this authenticationService.

If you want to use cbauth in cbsecurity you have to implement the cbsecurity.interfaces.IUserService.cfc interface.

I create a Userservice with commandbox which has 3 methods:

coldbox create model 
    name = UserService 
    methods = isValidCredentials,retrieveUserByUsername,retrieveUserById

I created a super simple UserService which can serve only two users johnDoe and admin with different permissions. I did this because I didn’t want to use a database here, but it is not hard to create something more realistic.

/**
* I am a simplified UserService
*/
component accessors="true" singleton {
	
  // Properties
  property name="wirebox" inject="wirebox";
  /**
  * Constructor
  */
  UserService function onDIComplete(){
    //some dummy initialization
    variables.Admin = wirebox.getInstance("User");
    variables.Admin.setId("AdminId");
    variables.Admin.setUserName("admin");
    variables.Admin.setPermissions("admin,user");
    variables.JohnDoe = wirebox.getInstance("User");
    variables.JohnDoe.setId("JohnDoeId");
    variables.JohnDoe.setUserName("JohnDoe");
    variables.JohnDoe.setPermissions("user");
    return this;
  }

  /**
  * isValidCredentials
  */
  function isValidCredentials( required username, required password ){
    // dummy users, only possible usernames: admin and johnDoe
    // we ignore the passwords for now. 
    // They can be set and encrypted with bcrypt module in a db.
      return listFindNoCase("admin,johndoe", arguments.username)
    }

  /**
  * retrieveUserByUsername
  */
  function retrieveUserByUsername( required UserName ){
    //usually you will get this from some persistent storage
    if (username eq "admin") return variables.Admin;
    if (username eq "johnDoe") return variables.JohnDoe
  }
  /**
  * retrieveUserById
  */
  function retrieveUserById( required id ){
    if (id eq "adminId") return variables.Admin;
    if (id eq "johnDoeId") return variables.JohnDoe
  }
}

Next on the list is a User object which should adhere to the cbsecurity.interfaces.IAuthUser interface. But beware of the differences: in a standalone cbauth module you only have to implement getId(). In a cbsecurity context you also have to provide a hasPermissions() and an isLoggedIn() method. I created a very simple User component, feel free to add more methods and data, as long as these methods are there you will be fine.

/**
* I am a User object with cbsecurity.interfaces.IAuthUser interface
*/
component accessors="true"{
	
  // Properties
  property name = "id" type="string";
  property name="username" type="string";
  // doesn't matter if you call this roles or permissions, 
  // it is just a list  (or array) of string values
  property name = "permissions" type="string";
  property name = "LoggedIn" type="boolean" default=false;

  /**
  * Constructor
  */
  User function init(){
    return this;
  }

  /**
  * hasPermission
  */
  function hasPermission( permission ){
    return listFindNoCase(variables.permissions, arguments.permission)
  }

  /**
  * isLoggedIn
  */
  function isLoggedIn(){
    return variables.LoggedIn;
  }
}

So now we have the required User and UserService components, we can configure our modules in config/ColdBox.cfc

// module setting overrides
moduleSettings = {
  cbAuth: {
    userServiceClass: "UserService"
  },
  // CB Security
  cbSecurity : {
    // The global invalid authentication event or URI or URL to go 
    // if an invalid authentication occurs
    "invalidAuthenticationEvent" : "auth.index",
    // The global invalid authorization event or URI or URL to go 
    // if an invalid authorization occurs
    "invalidAuthorizationEvent"	 : "main.onInvalidAuthorization",
    // The validator is an object that will validate rules 
    // and annotations and provide feedback on either authentication or 
    // authorization issues.
    // WireBox ID of the user service to use
    "userService"                : "UserService",
    // Force SSL for all relocations
    "useSSL"			: false,
    // Activate handler/action based annotation security
    "handlerAnnotationSecurity"		: true
};

As you can see from the above settings, you have to configure BOTH cbAuth and cbSecurity. cbAuth settings are short, for cbSecurity many more options available but for our annotation based setup this will do.

Protecting your event handlers and actions

So now we have our security up and running. We created a user johnDoe with user permissions and a user admin with user and admin permissions. Time to protect some actions. I created a handler SecuredHandler with some simple events.

component {
  /**
  * index
  */
  function index( event, rc, prc ) secured="admin" {
    event.setView( "SecuredHandler/index" );
  }
  /**
  * doSomething
  */
  function doSomething( event, rc, prc ) secured="user" {
    event.setView( "SecuredHandler/doSomething" );
  }
  /**
  * doSomethingElse
  */
  function doSomethingElse( event, rc, prc ){
    event.setView( "SecuredHandler/doSomethingElse" );
  }
}

As you can see on the highlighted lines I added the annotation secured="someRole" to the functions. This way you can only access these actions if you are authenticated and have the correct role or permission. If you want to protect the whole handler, you can add the secured keyword to the component itself

There’s still one thing missing. As long as you cannot login, you will not be able to access these actions. For this I created the Auth handler.

component{

  /**
  * index
  */
  function index( event, rc, prc ){
    event.setView("Auth/index");
  }

  /**
  * login
  */
  function login( event, rc, prc ){
    auth().authenticate(rc.username,rc.password);
    relocate("Auth.index")
  }

  /**
  * logout
  */
  function logout( event, rc, prc ){
    auth().logout();
    relocate("Auth.index")
  }
}

As you can see from the highlighted code, I use the auth().authenticate() and auth().logout() methods for login and logout.

Rules or annotations?

In this sample I showed cbauth with annotation based authorization. Annotations are simple, but not always flexible. If you want to be able to change your authorization roles for certain events on the fly, or if you want to use authorization based on your URL’s instead it probably makes more sense to use rules, which I will explore in a future post.

More…

A simple demo app for cbvalidation can be found on github. Feel free to experiment. Feel free to ask or comment, here or on the cfml or boxteam slack

Cbsecurity: