Only this pageAll pages
Powered by GitBook
1 of 55

v2.x

Loading...

Intro

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Getting Started

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Usage

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Security Validators

Loading...

Loading...

Loading...

JWT

Loading...

Loading...

Loading...

Loading...

Loading...

External links

Introduction

The ColdBox cbsecurity module is a collection of modules to help secure your applications.

The major areas of concern are:

  • A security authentication/authorization firewall ( cbsecurity ) which can secure your application based on:

    • Security rules and a rule engine for validation incoming events or URL's

    • Handler annotations

  • A security service for explicit authorizations ( cbsecurity ) to provide you with functional approaches to security context authorization in any layer of your application.

  • A JWT generator, decoder and authentication services ( jwtcfml )

  • Cross Site Request Forgery (CSRF) Protection ( cbcsrf )

  • An authentication manager ( cbauth )

Module composition

Cbsecurity consumes several other modules and leverages cbstorages for storage.

Features

  • Ability to have global security rules

  • Ability for modules to add their own security rules and action overrides

  • Ability to distinguish between authentication and authorization issues

  • Annotation driven cascading security for handlers and actions

  • A functional security service that can be injected anywhere to provide you with authorizations

  • Security rules can exist in:

    • XML File

    • JSON File

    • Database

    • Models

  • The rules can be configured to use regular expressions or simple snippets

  • Can use ColdFusion authentication security

  • Can leverage any custom authentication provider

  • Plug any Authentication service or can leverage cbauth by default

  • Capability to distinguish between invalid authentication and invalid authorization and determine an outcome of the process.

  • Ability to load/unload security rules from contributing modules.

  • Ability for each module to define it's own validator

  • JWT Access and Refresh Tokens Native support

Versioning

The ColdBox Security Module is maintained under the Semantic Versioning guidelines as much as possible. Releases will be numbered with the following format:

<major>.<minor>.<patch>

And constructed with the following guidelines:

  • Breaking backward compatibility bumps the major (and resets the minor and patch)

  • New additions without breaking backward compatibility bumps the minor (and resets the patch)

  • Bug fixes and misc changes bumps the patch

License

Apache 2 License: http://www.apache.org/licenses/LICENSE-2.0​

Important Links

  • Code: https://github.com/coldbox-modules/cbsecurity​

  • Issues: https://github.com/coldbox-modules/cbsecurity/issues

Professional Open Source

Ortus Solutions, Corp

The ColdBox Security Module is a professional open source software backed by Ortus Solutions, Corp offering services like:

  • Custom Development

  • Professional Support & Mentoring

  • Training

  • Server Tuning

  • Security Hardening

  • Code Reviews

  • Much More

Discussion & Help

The Box products and modules community for discussion and help can be found here:

https://community.ortussolutions.com/c/box-modules/cbsecurity/

HONOR GOES TO GOD ABOVE ALL

Because of His grace, this project exists. If you don't like this, then don't read it, it's not for you.

"Therefore being justified by faith, we have peace with God through our Lord Jesus Christ: By whom also we have access by faith into this grace wherein we stand, and rejoice in hope of the glory of God." Romans 5:5

What's New With 2.7.0

2020-SEP-14

Added

  • Contributed module rules are now pre-pended instead of appended. (@wpdebruin)

Fixed

  • Not loading rules by source file detection due to invalid setting check

  • Don't trigger ColdBox's invalid event looping protection. It also auto-senses between ColdBox 6 and 5 (@homestar9)

  • Fixed token scopes according to JWT spec, it is called scope and it is a space separated list. This doesn't change the User interface for it. (@wpdebruin)

  • Update token storages so no token rejection anymore when storage is not enabled. (@wpdebruin)

What's New With 2.12.0

2021-MAR-29

Added

  • More and more apps will need real ip's from request, so expose it via the CBSecurity model service as : getRealIp()

Fixed

  • When using getHTTPREquestData() send false so we DON'T retrieve the http body when we just need the headers

  • More updates to getRealIp() when dealing with lists

What's New With 2.9.0

2020-DEC-11

Fixed

  • Fixes a typo in the cbSecurity_onInvalidAuthorization interception point declaration. Previously, the typo would prevent ColdBox from allowing the correctly-typed interception point from ever triggering an interception listener.

  • The userValidator() method has been changed to roleValidator(), but the error message was forgotten! So the developer is told they need a userValidator() method... because the userValidator method is no longer supported. :/

Added

  • The isLoggedIn() method now makes sure that a jwt is in place and valid, before determining if you are logged in or not.

  • Migrated all automated tests to focal and mysql8 in preparation for latest updates

  • Add support for JSON/XML/model rules source when loading rules from modules. Each module can now load rules not only inline but from the documented external sources.

  • Ensure non-configured rules default to empty array

What's New With 2.2.0

2020-FEB-12

Features

  • Feature : Migrated from the jwt to the jwtcfml (https://forgebox.io/view/jwt-cfml) library to expand encoding/decoding capabilities to support RS and ES algorithms:

    • HS256

    • HS384

    • HS512

    • RS256

    • RS384

    • RS512

    • ES256

    • ES384

    • ES512

  • Feature : Added a new convenience method on the JWT Service: isTokenInStorage( token ) to verify if a token still exists in the token storage

  • Feature : If no jwt secret is given in the settings, we will dynamically generate one that will last for the duration of the application scope.

  • Feature : New setting for jwt struct: issuer, you can now set the issuer of tokens string or if not set, then cbSecurity will use the home page URI as the issuer of authority string.

  • Feature : All tokens will be validated that the same iss (Issuer) has granted the token

Improvements

  • Improve : Ability to have defaults for all JWT settings instead of always typing them in the configs

  • Improve : More formattting goodness!

Bugs

  • Bug : Invalidation of tokens was not happening due to not using the actual key for the storage

CBAuth Validator

ColdBox security ships with the cbauth validator that knows how to talk to the authentication service and validate authentication and authorization via permissions. All you need to do is use the WireBox ID of CBAuthValidator@cbsecurity in your validator setting:

cbsecurity = {

    validator = "CBAuthValidator@cbsecurity"

}

CBAuthValidator is the default validator for ColdBox Security

Just make sure your User object adheres to our interface of IAuthUser.

Author

Luis Fernando Majano Lainez

Luis Majano is a Computer Engineer that has been developing and designing software systems since the year 2000. He was born in in the late 70’s, during a period of economical instability and civil war. He lived in El Salvador until 1995 and then moved to Miami, Florida where he completed his Bachelors of Science in Computer Engineering at . Luis resides in Houston, Texas with his beautiful wife Veronica, baby girl Alexia and baby boy Lucas!

He is the CEO of , a consulting firm specializing in web development, ColdFusion (CFML), Java development and all open source professional services under the ColdBox and ContentBox stack. He is the creator of ColdBox, ContentBox, WireBox, MockBox, LogBox and anything “BOX”, and contributes to many open source ColdFusion/Java projects. You can read his blog at

Luis has a passion for Jesus, tennis, golf, volleyball and anything electronic. Random Author Facts:

  • He played volleyball in the Salvadorean National Team at the tender age of 17

  • The Lord of the Rings and The Hobbit is something he reads every 5 years. (Geek!)

  • His first ever computer was a Texas Instrument TI-86 that his parents gave him in 1986. After some time digesting his very first BASIC book, he had written his own tic-tac-toe game at the age of 9. (Extra geek!)

  • He has a geek love for circuits, microcontrollers and overall embedded systems.

  • He has of late (during old age) become a fan of organic gardening.

Keep Jesus number one in your life and in your heart. I did and it changed my life from desolation, defeat and failure to an abundant life full of love, thankfulness, joy and overwhelming peace. As this world breathes failure and fear upon any life, Jesus brings power, love and a sound mind to everybody!

“Trust in the LORD with all your heart, and do not lean on your own understanding.” Proverbs 3:5

Contributors

Will de Bruin

Brad Wood

Authorization Contexts

There are also times where you need to validate custom conditions and block access to certain areas. This way, you can implement your own custom security logic and leverage cbSecurity for blockage. You will accomplish this via the secureWhen() method:

The context can be a closure/lambda/udf or a boolean evaluation:

The closure/udf will receive the currently authenticated user as the first argument.

Securing Views

You can also use our handy event.secureView() method in the request context to pivot between views according to user permissions.

cbSecurity injects the secureView() method into the request context via the preProcess interception point.

This will allow you to set the successView if the user has the permissions or the failView if they don't.

secureWhen( context, [errorMessage] )
// Using as a closure/lambda
cbSecurity.secureWhen( ( user ) => !user.isConfirmed() )
cbSecurity.secureWhen( ( user ) => !oEntry.canPublish( user ) )

// Using a boolean evaluation
cbSecurity.secureWhen( cbSecurity.none( "AUTHOR_ADMIN" ) && !cbSecurity.sameUser( oAuthor )  )
cbSecurity.whenNone( "AUTHOR_ADMIN", ( user ) => relocate() );
( user ) => {}
function( user );
event.secureView( permissions, successView, failView )

XML Rules

If you have already an XML file with your rules, then all you need to do is add the path (relative or absolute) to that file in the rules configuration key. However, the path MUST include the keyword XML in it.

config/Coldbox.cfc
moduleSettings = {
	// CB Security
	cbSecurity : {
		"rules" : "config/security.xml.cfm"
};

Then your xml file can look like this:

config/security.xml.cfm
<?xml version="1.0" encoding="ISO-8859-1"?>
<-- <
Declare as many rule elements as you want, order is important 
Remember that the securelist can contain a list of regular
expressions if you want

ex: All events in the user handler
 user\..*
ex: All events
 .*
ex: All events that start with admin
 ^admin

If you are not using regular expressions, just write the text
that can be found in an event.
-->
<rules>
    <rule>
        <match>event</match>
        <whitelist>user\.login,user\.logout,^main.*</whitelist>
        <securelist>^user\..*, ^admin</securelist>
        <roles>admin</roles>
        <permissions>read,write</permissions>
        <redirect>user.login</redirect>
    </rule>

    <rule>
           <match>event</match>
        <whitelist></whitelist>
        <securelist>^moderator</securelist>
        <roles>admin,moderator</roles>
        <permissions>read</permissions>
        <redirect>user.login</redirect>
    </rule>

    <rule>
           <match>url</match>
        <whitelist></whitelist>
        <securelist>/secured.*</securelist>
        <roles>admin,paid_subscriber</roles>
        <permissions></permissions>
        <redirect>user.pay</redirect>
    </rule>
</rules>

Secured URL

_securedURL

The security module has the concept of a secured URL which is the actual URL that got intercepted and relocated because of a security exception. If the module detects an invalid authentication or authorization and an action must be issued, then the firewall will store this URL in the RC scope and flash it so it can be available in the next request (if a relocation occurs).

The flash RAM variable is called: _securedURL. This key will be persisted in the flash memory of the framework and when the user gets relocated to the redirect element, this key will be populated in the request collection automatically for you.

So always remember to use this key if you want to provide a seamless login experience to your users. You can easily place it in the login form:

#html.startForm(action=prc.xehDoLogin,name="loginForm")#

    #html.hiddenField(name="_securedURL",value=event.getValue('_securedURL',''))#

    #html.textfield(name="username",label="Username: ",size="40",required="required",class="textfield",value=prc.rememberMe)#
    #html.passwordField(name="password",label="Password: ",size="40",required="required",class="textfield")#

    <div id="loginButtonbar">
        #html.checkBox(name="rememberMe",value=true,checked=(len(prc.rememberMe)))# 
        #html.label(field="rememberMe",content="Remember Me  ",class="inline")#
        #html.submitButton(value="  Log In  ",class="buttonred")#
    </div>

    <br/>
    <img src="#prc.cbRoot#/includes/images/lock.png" alt="lostPassword" />
    <a href="#event.buildLink(prc.xehLostPassword)#">Lost your password?</a> 

#html.endForm()#

JSON Rules

If you have already a JSON file with your rules, then all you need to do is add the path (relative or absolute) to that file in the rules configuration key. However, the path MUST include the keyword json in it.

config/Coldbox.cfc
moduleSettings = {
	// CB Security
	cbSecurity : {
		"rules" : "config/security.json.cfm"
};

Then your file can be something like this:

config/security.json.cfm
[
    {
        "whitelist": "user\\.login,user\\.logout,^main.*",
        "securelist": "^user\\.*, ^admin",
        "match": "event",
        "roles": "admin",
        "permissions": "",
        "redirect": "user.login",
        "useSSL": false
    },
    {
        "whitelist": "",
        "securelist": "^shopping",
        "match": "url",
        "roles": "",
        "permissions": "shop,checkout",
        "redirect": "user.login",
        "useSSL": true
    }
]
San Salvador, El Salvador
Florida International University
Ortus Solutions
www.luismajano.com

DB Rules

If you have your security rules in a database, then cbsecurity can read the rules from the database for you. Just make the rules key equal to db and fill out the extra configuration keys shown below:

secure() Blocking Methods

The secure() Methods

Now that you have access to the model, you can use the following method to verify explicit permissions and authorize access. This method will throw an exception if the user does not validate the incoming permissions context (NotAuthorized).

  • The permission can be an array, string or list of the permissions to validate. The user must have at least one of the permissions specified.

  • The message is a custom error message to be used in the message string of the exception thrown.

You also have two more authorization methods that will verify certain permission conditions for you:

Conditional Authorizations Using when()

There are also cases where you want to execute a piece of code by determining if the user has access to do so. For example, only a USER_ADMIN can change people's roles or you want to filter some data for certain users. For this, we have created the when() method with the following signature:

  • The permissions is a permission array or list that will be Or'ed

  • The success is a closure/lambda or UDF that will execute if the permissions validate.

  • The fail is a closure/lambda or UDF that will execute if the permissions DID not validate, much like an else statement

Both closures/functions takes in a user which is the currently authenticated user, the called in permissions and can return anything.

You can also chain the when() calls if needed, to create beautiful security contexts. So if we go back to our admin examples, we can do something like this:

We have also added the following whenX() methods to serve your needs when evaluating the permissions:

Interceptions

The security firewall will announce some interception events when invalid access or authorizations occur within the system:

  • cbSecurity_onInvalidAuthentication

  • cbSecurity_onInvalidAuthorization

You will receive the following data in the interceptData struct in each interception call:

  • ip : The offending IP address

  • rule : The security rule intercepted or empty if annotations

  • settings : The firewall settings

  • validatorResults : The validator results

  • annotationType : The annotation type intercepted, handler or action or empty if rule driven

  • processActions : A Boolean indicator that defaults to true. If you change this to false, then the interceptor won't fire the invalid actions. Usually this means, you manually will do them.

With these interceptions you can build a nice auditing system, login tracking and much more.

Stop Processing Actions

The intercept data has a key called processActions which defaults to true. This Boolean indicator tells the firewall to process the invalid authentication/authorization procedures. If you change this value to false, then the firewall will do NOTHING because it is expecting for YOU to have done the actions.

JWT Interception

If you are using our , then we will announce the following interceptions during JWT usage:

  • cbSecurity_onJWTCreation

  • cbSecurity_onJWTInvalidation

  • cbSecurity_onJWTValidAuthentication

  • cbSecurity_onJWTInvalidUser

  • cbSecurity_onJWTInvalidClaims

  • cbSecurity_onJWTExpiration

  • cbSecurity_onJWTStorageRejection

  • cbSecurity_onJWTValidParsing

  • cbSecurity_onJWTInvalidateAllTokens

Check them all out in our .

CBAuth Interceptions

You can always find the latest interception points here:

cbauth announces several custom interception points. You can use these interception points to change request data or add additional values to session or request scopes. The preAuthentication and postAuthentication events fire during the standard authenticate() method call with a username and password. The preLogin and postLogin events fire during the login() method call. The preLogout and postLogout events fire during the logout() method call.

The preLogin and postLogin interception points will be called during the course of authenticate(). The order of the calls then are preAuthentication -> preLogin -> postLogin -> postAuthentication.

Verification Methods

If you just want to validate if a user has certain permissions or maybe no permissions at all or if a passed user is the same as the logged in user, then you can use the following boolean methods that only do verification.

Please note that you could potentially do these type of methods by leveraging the currently logged in user and it's hasPermission() method. However, these methods provide abstraction and can easily be mocked!

These are great to have a unified and abstracted way to verifying permissions or if the passed user is the same as the logged in user. Here are some examples:

View Layer

Other Layers:

Please note that we do user equality by calling the getId() method of the authenticated user and the incoming user. This is part of our IAuthUser interface requirements.

JWT Validator

Now that we have all the pieces in place for JWT, we can now register the JWT validator as our validator of choice for requests which in our case it is the same JWT Service that will take care of the validation: JWTService@cbsecurity.

The validator will inspect the incoming requests for valid jwt authorization headers. It will be in charge of verifying their expiration, their required claims, and the user it represents. Once that is done, it goes in the same rule/annotation security flow that cbsecurity leverages.

Module Override

Each module can also override their validator via it's configuration setting cbsecurity.validator. So if the global validator is something other than jwt but your module REQUIRES JWT validation, then just add it in your ModuleConfig.cfc

JWT Token Discovery

The JWT validator will discover the incoming JWT token from 3 sources:

  1. authorization header using the bearer token approach

  2. Custom header configured in your settings: cbsecurity.customAuthHeader

  3. Incoming rc variable with the same name as cbsecurity.customAuthHeader

Token Scopes & Permissions

If your rules have the permissions element or your secure annotations have context, then we will treat those as the scopes/permissions to check the user/token must have at validation.

Validator Process

The validator will have the following validation process:

  • Verify the jwt token exists via the authorization header or custom header x-auth-token or incoming rc[ 'x-auth-token' ]

  • Verify we can decode it

  • Verify if it has not expired from the token itself

    • If you have enabled auto refresh tokens, check out the .

  • Verify it has the required claims

  • If token storage is enabled, verify the token in the permanent storage

  • Verify the subject (sub) claim and try to retrieve the user it represents

  • Try to authenticate the user for the request

  • Verify the subject has the right permissions or the token has the right scopes attached to it.

  • If all is valid then place the token in prc.jwt_token and the payload in prc.jwt_payload

  • If all is valid then place the user object in prc.oCurrentUser or the variable of your choice via the cbsecurity.prcUserVariable setting.

  • Continue or block

That's it! You can create your rules and annotations just like your used to, but now the validator will make sure valid JWT tokens are passed for those requests.

Property

Type

Required

Default

Description

rulesDSN

string

true

---

The dsn to use if the rules are coming from a database

rulesTable

string

true

---

The table where the rules are

rulesSQL

string

false

select* from rulesTable

The custom SQL statement to use to retrieve the rules according to the rulesTable property. If not set, the default of select* from rulesTable will be used.

rulesOrderBy

string

false

---

The column to order the rules by. If not chosen, the interceptor will not order the query, just select it.

config/Coldbox.cfc
moduleSettings = {
	// CB Security
	cbSecurity : {
		rules        : "db", // Rules are in the database
		rulesDSN     : "myDatasource", // The datasource
		rulesTable   : "securityRules", // The table that has the rules
		rulesOrderBy : "order asc" // An optional ordering
	}
};
// Verify the currently logged in user has at least one of those permissions, 
// else throw a NotAuthorized exception
cbSecurity.secure( permissions, [message] );
cbsecure().secure( permissions, [message] );
// Authorize that the user has ALL of the incoming permissions
cbSecurity.secureAll( permissions, [message] );
// Authorize that the user has NONE of the incoming permissions
cbSecurity.secureNone( permissions, [message] );
when( permissions, success, fail )
// Lambda approach
( user, permissions ) => { 
    // your code here
};
// UDF/Closure
function( user, permissions ){ 
    // your code here
}
var oAuthor = authorService.getOrFail( rc.authorId );
prc.data = userService.getData();

// Run Security Contexts
cbSecure()
    // Only user admin can change to the incoming role
    .when( "USER_ADMIN", ( user ) => oAuthor.setRole( roleService.get( rc.roleID ) ) )
    // The system admin can set a super admin
    .when( "SYSTEM_ADMIN", ( user ) => oAuthor.setRole( roleService.getSystemAdmin() ) )
    // Filter the data to be shown to the user
    .when( "USER_READ_ONLY", ( user ) => prc.data.filter( ( i ) => !i.isClassified ) )

// Calling with a fail closure
cbSecurity.when(
    "USER_ADMIN",
    ( user ) => user.setRole( "admin" ), //success
    ( user ) => relocate( "Invaliduser" ) //fail
);
// When all permissions must exist in the user
whenAll( permissoins, success, fail)
// When none of the permissions exist in the user
whenNone( permissions, success, fail )
// Checks the user has one or at least one permission if the 
// permission is a list or array
boolean cbSecurity.has( permission );
// The user must have ALL the permissions
boolean cbSecurity.all( permission );
// The user must NOT have any of the permissions
boolean cbSecurity.none( permission );
// Verify if the passed in user is the same as the logged in user
boolean cbSecurity.sameUser( user );
<cfif cbsecure().has( "USER_ADMIN" )>
    This is only visible to user admins!
</cfif>

<cfif cbsecure().has( "SYSTEM_ADMIN" )>
    <a href="/user/impersonate/#prc.user.getId()#">Impersonate User</a>
</cfif>

<cfif cbsecure().sameUser( prc.user )>
    <i class="fa fa-star">This is You!</i>
</cfif>
if( cbSecurity.has( "PERM" ) ){
    auditUser();
}

if( cbSecurity.sameUser( prc.incomingUser ) ){
    // you can change your gravatar
}

What's New With 2.0.0

2019-SEP-25

New Features

  • Adobe 2016,2018 Support

  • Settings transferred to ColdBox 4/5 moduleSettings approach instead of root approach (See compat section)

  • The rulesModelMethod now defaults to getSecurityRules()

  • ColdFusion security validator has an identity now CFValidator@cbsecurity instead of always being inline.

  • You can now add an overrideEvent element to a rule. If that is set, then we will override the incoming event via event.overrideEvent() instead of doing a relocation using the redirect rule element.

  • You can now declare your rules inline in the configuration settings using the rules key. This will allow you to build the rules in your config instead of a rule source.

  • We now can distinguish between invalid auth and invalid authorizations

  • New interception block points cbSecurity_onInvalidAuthentication, cbSecurity_onInvalidAuhtorization

  • You now have a defaultAuthorizationAction setting which defaults to redirect

  • You now have a invalidAuthenticationEvent setting that can be used

  • You now have a defaultAuthenticationAction setting which defaults to redirect

  • You now have a invalidAuthorizationEvent setting that can be used

  • If a rule is matched, we will store it in the prc as cbSecurity_matchedRule so you can see which security rule was used for processing invalid access actions.

  • If a rule is matched we will store the validator results in prc as cbSecurity_validatorResults

  • Ability for modules to register cbSecurity rules and setting overrides by registering a settings.cbSecurity key.

  • New security rule visualizer for graphically seeing you rules and configuration. Can be locked down via the enableSecurityVisualizer setting. Disabled by default.

  • Annotation based security for handlers and actions using the secured annotation. Which can be boolean or a list of permissions, roles or whatever you like.

  • You can disable annotation based security by using the handlerAnnotationSecurity boolean setting.

  • JWT Token Security Support

Improvements

  • SSL Enforcement now cascades according to the following lookup: Global, rule, request

  • Interfaces documented for easier extension interfaces.*

  • Migration to script and code modernization

  • New Module Layout

  • Secured rules are now logged as warn() with the offending Ip address.

  • New setting to turn on/off the loading of the security firewall: autoLoadFirewall. The interceptor will auto load and be registered as cbsecurity@global in WireBox.

Compat

  • Adobe 11 Dropped

  • Lucee 4.5 Dropped

  • Migrate your root cbSecurity settings in your config/ColdBox.cfc to inside the moduleSettings

  • IOC rules support dropped

  • OCM rules support dropped

  • validatorModel dropped in favor of just validator to be a WireBox Id

  • Removed preEventSecurity it was too chatty and almost never used

  • The function userValidator has been renamed to ruleValidator and also added the annotationValidator as well.

  • rulesSource removed you can now use the rules setting

    • The rules can be: array, db, model, filepath

    • If the filepath has json or xml in it, we will use that as the source style

  • rulesFile removed you can now use the rules setting.

interceptors/SecurityAudit.cfc
component extends="coldbox.system.Interceptor"{

    function cbSecurity_onInvalidAuthentication( event, interceptData ){
        // do what you like here
    }
    
    function cbSecurity_onInvalidAuthorization( event, interceptData ){
        // do what you like here
    }

}
JWT facilities
JWT Interceptions Page
config/Coldbox.cfc
cbsecurity = {
    validator = "JWTService@cbsecurity"
}
settings = {
    cbsecurity = {
         validator = "JWTService@cbsecurity"
    }
}
refresh tokens process

Security Annotations

The security module also allows you to secure your events via annotations instead of using security rules. The setting that controls this security feature is the handlerAnnotationSecurity which can see in the configuration section.

The security module has a tiered approach to annotation security as it will check the handler component first and then the requested action method second. You can apply different security contexts to each level as you see fit.

Please note that the security rules will be inspected first, annotations second.

See the diagram below for inspecting security based on annotations:

Annotation based security

Secure Annotation

The firewall will inspect handlers for a secured annotation. This annotation can be added to the entire handler or to an action method or both. The default value of the secured annotation is a Boolean true. Which means, we need a user to be authenticated in order to access it.

// Secure the entire handler
component secured{

	function index(event,rc,prc){}
	function list(event,rc,prc){}

}
// Same as this
component secured=true{
}

// Do NOT secure the handler
component secured=false{
}
// Same as this, no annotation!
component{

	function index(event,rc,prc) secured{
	}

	function list(event,rc,prc) secured="list"{

	}
	
}

Authorization Context

You can also give the annotation a value, which can be anything you like: A list of roles, a role, a list of permissions, metadata, JSON, etc. Whatever it is, this is called the authorization context and the user validator must be able to not only authenticate but authorize the context or an invalid authorization will occur.

// Secure this handler
component secured="admin,users"{

	function index(event,rc,prc) secured="list"{

	}
	
	function save(event,rc,prc) secured="write"{

	}

}

The secured value will be passed to the validator's for authorization.

Cascading Security

By having the ability to annotate the handler and also the action you create a cascading security model where they need to be able to access the handler first and only then will the action be evaluated for access as well.

About This Book

About This Book

The source code for this book is hosted in GitHub: https://github.com/ortus-docs/cbsecurity-docs. You can freely contribute to it and submit pull requests. The contents of this book is copyright by Ortus Solutions, Corp and cannot be altered or reproduced without author's consent. All content is provided "As-Is" and can be freely distributed.

  • The majority of code examples in this book are done in cfscript.

  • The majority of code generation and running of examples are done via CommandBox: The ColdFusion (CFML) CLI, Package Manager, REPL - https://www.ortussolutions.com/products/commandbox​

External Trademarks & Copyrights

Flash, Flex, ColdFusion, and Adobe are registered trademarks and copyrights of Adobe Systems, Inc.

Notice of Liability

The information in this book is distributed “as is”, without warranty. The author and Ortus Solutions, Corp shall not have any liability to any person or entity with respect to loss or damage caused or alleged to be caused directly or indirectly by the content of this training book, software and resources described in it.

Contributing

We highly encourage contribution to this book and our open source software. The source code for this book can be found in our GitHub repository where you can submit pull requests.

Charitable Proceeds

10% of the proceeds of this book will go to charity to support orphaned kids in El Salvador - https://www.harvesting.org/. So please donate and purchase the printed version of this book, every book sold can help a child for almost 2 months.

Shalom Children's Home

Shalom Children’s Home is one of the ministries that is dear to our hearts located in El Salvador. During the 12 year civil war that ended in 1990, many children were left orphaned or abandoned by parents who fled El Salvador. The Benners saw the need to help these children and received 13 children in 1982. Little by little, more children came on their own, churches and the government brought children to them for care, and the Shalom Children’s Home was founded.

Shalom now cares for over 80 children in El Salvador, from newborns to 18 years old. They receive shelter, clothing, food, medical care, education and life skills training in a Christian environment. The home is supported by a child sponsorship program.

We have personally supported Shalom for over 6 years now; it is a place of blessing for many children in El Salvador that either have no families or have been abandoned. This is good earth to seed and plant.

Module Rules

Every module in ColdBox has the capability to contribute their own rules to cbsecurity by registering them in the ModuleConfig.cfc within the settings struct. Just create another struct called cbsecurity with the following allowed keys:

As you can see each module can have it's own overrides for authentication and authorization events as well as their own rules.

Please note that these security rules will be PREPENDED to the global rules

Rule Sources

As with the global rules defined in config/Coldbox.cfc, the module cbsecurity.rules setting supports multiple rule sources:

For example, you can load security rules specific to a module from a JSON file stored in your module:

Loading/Unloading

Also note that if modules are loaded dynamically, it will still inspect them and register them if cbsecurity settings are found. The same goes for unloading, the entire security rules for that module will cease to exist.

ModuleConfig.cfc
settings = {
    // CB Security Rules to prepend to global rules
    cbsecurity = {
        // Module Relocation when an invalid access is detected, instead of each rule declaring one.
        "invalidAuthenticationEvent"     : "mod1:secure.index",
        // Default Authentication Action: override or redirect when a user has not logged in
        "defaultAuthenticationAction"    : "redirect",
        // Module override event when an invalid access is detected, instead of each rule declaring one.
        "invalidAuthorizationEvent"    : "mod1:secure.auth",
        // Default Authorization Action: override or redirect when a user does not have enough permissions to access something
        "defaultAuthorizationAction"    : "redirect",
        // You can define your security rules here
        "rules"                            : [
            {
                "secureList"     : "mod1:home"
            },
            {
                "secureList"     : "mod1/modOverride",
                "match"            : "url",
                "action"        : "override"
            }
        ]
    }
};
ModuleConfig.cfc
settings = {
    cbsecurity = {
        "rules" : "#modulePath#/config/firewallRules.json"
        // other config here... <---
    }
};
DB
Inline
JSON
Model
XML

What's New With 2.4.0

2020-APR-02

This release adds the inclusion of the Cross Site Request Forgery module into cbsecurity: cbcsrf. You can find all the details about this module here: https://github.com/coldbox-modules/cbcsrf. Below are the major features of this module:

Features

  • Ability to generate security tokens based on your session

  • Automatic token rotation when leveraging cbauth login and logout operations

  • Ability to on-demand rotate all security tokens for specific users

  • Leverages cbStorages to store your tokens in CacheBox, which can be easily distributed and clustered

  • Ability to create multiple tokens via unique reference keys

  • Auto-verification interceptor that will verify all non-GET operations to ensure a security token is passed via rc or headers

  • Auto-sensing of integration testing so the verifier can allow testing calls

  • Token automatic rotation on specific time periods for enhance security

  • Helpers to automatically generate hidden fields for the token

  • Automatic generation endpoint that can be used for Ajax applications to request tokens for users

Configuration

Security Settings

By Default, the security module will register itself for you using the module configuration settings you define in theconfig/ColdBox.cfc. Below you can find all the settings with their default value and description.

config/Coldbox.cfc
// Module Settings
moduleSettings = {
    // CB Security
    cbSecurity : {
        // The global invalid authentication event or URI or URL to go if an invalid authentication occurs
        "invalidAuthenticationEvent"    : "",
        // Default Authentication Action: override or redirect when a user has not logged in
        "defaultAuthenticationAction"    : "redirect",
        // The global invalid authorization event or URI or URL to go if an invalid authorization occurs
        "invalidAuthorizationEvent"        : "",
        // Default Authorization Action: override or redirect when a user does not have enough permissions to access something
        "defaultAuthorizationAction"    : "redirect",
        // You can define your security rules here or externally via a source
        // specify an array for inline, or a string (db|json|xml|model) for externally
        "rules"                            : [],
        // The validator is an object that will validate rules and annotations and provide feedback on either authentication or authorization issues.
        "validator"                        : "CBAuthValidator@cbsecurity",
        // The WireBox ID of the authentication service to use in cbSecurity which must adhere to the cbsecurity.interfaces.IAuthService interface.
        "authenticationService"          : "authenticationService@cbauth",
        // WireBox ID of the user service to use
        "userService"                     : "",
        // The name of the variable to use to store an authenticated user in prc scope if using a validator that supports it.
        "prcUserVariable"                 : "oCurrentUser",
        // If source is model, the wirebox Id to use for retrieving the rules
        "rulesModel"                    : "",
        // If source is model, then the name of the method to get the rules, we default to `getSecurityRules`
        "rulesModelMethod"                : "getSecurityRules",
        // If source is db then the datasource name to use
        "rulesDSN"                        : "",
        // If source is db then the table to get the rules from
        "rulesTable"                    : "",
        // If source is db then the ordering of the select
        "rulesOrderBy"                    : "",
        // If source is db then you can have your custom select SQL
        "rulesSql"                         : "",
        // Use regular expression matching on the rule match types
        "useRegex"                         : true,
        // Force SSL for all relocations
        "useSSL"                        : false,
        // Auto load the global security firewall
        "autoLoadFirewall"                : true,
        // Activate handler/action based annotation security
        "handlerAnnotationSecurity"        : true,
        // Activate security rule visualizer, defaults to false by default
        "enableSecurityVisualizer"        : false,
        // JWT Settings
        "jwt"                             : {
            // The issuer authority for the tokens, placed in the `iss` claim
            "issuer"                  : "",
            // The jwt secret encoding key to use
            "secretKey"               : getSystemSetting( "JWT_SECRET", "" ),
            // by default it uses the authorization bearer header, but you can also pass a custom one as well or as an rc variable.
            "customAuthHeader"        : "x-auth-token",
            // The expiration in minutes for the jwt tokens
            "expiration"              : 60,
            // If true, enables refresh tokens, longer lived tokens (not implemented yet)
            "enableRefreshTokens"     : false,
            // The default expiration for refresh tokens, defaults to 30 days
            "refreshExpiration"       : 43200,
            // encryption algorithm to use, valid algorithms are: HS256, HS384, and HS512
            "algorithm"               : "HS512",
            // Which claims neds to be present on the jwt token or `TokenInvalidException` upon verification and decoding
            "requiredClaims"          : [] ,
            // The token storage settings
            "tokenStorage"            : {
                // enable or not, default is true
                "enabled"       : true,
                // A cache key prefix to use when storing the tokens
                "keyPrefix"     : "cbjwt_",
                // The driver to use: db, cachebox or a WireBox ID
                "driver"        : "cachebox",
                // Driver specific properties
                "properties"    : {
                    "cacheName" : "default"
                }
            }
        }
    }
};

InvalidAuthentication-/ InvalidAuthorization events and default actions

The invalidAuthenticationEvent and invalidAuthorizationEvent keys can be used to provide default events when Authentication or Authorization failed. The defaultAuthenticationAction and defaultAuthorizationAction determine whether there will be a redirection or override. The default action is redirect, but especially for API's an override will be more appropriate. When using rule-based security you can override these keys for any individual rule.

Validator

You can place a global validator in the configuration settings, but you can also override the validator on a module by module basis as well. The default validator is using the CBAuth Validator.

Authentication Services

cbsecurity ships with the cbauth module that can provide you with a nice interface for authentication services. If you use the default authenticationService authenticationService@cbauth, you have to define the UserServiceClass in the cbauth module. However, you can plug in any WireBox ID and select your own authentication services.

If you are using cbauth as your authenticationService (the default), you also need to configure cbauth.

User Services

cbsecurity will also require a user service if you will be dealing with any JWT security tokens. Just add your WireBox ID to the user service of your choice. If you are using cbauth, you have to define the UserServiceClass in the cbauth module.

Automatic Firewall

Please note that by default, the security firewall will be auto-registered for you. If you do NOT want the firewall to be automatically registered for you, then use the autoLoadFirewall setting and make it false. Then you can use the Custom Firewall approach below to register the firewall manually in the order of the interceptors that you would like.

autoLoadFirewall : false

Annotation Security

By default, annotation security is enabled. This will inspect ALL incoming event executions for the security annotations. If you do not want to use annotation security we recommend you turn it off to avoid the inspection of events.

handlerAnnotationSecurity : false

Security Visualizer

ColdBox security comes with a nice graphical visualizer for all the registered security rules and settings in your global firewall. You can enable it by using the enableSecurityVisualizer setting.

enableSecurityVisualizer :  true

You can then visit the /cbsecurity URL and you will be presented with this magical tool:

Important The visualizer is disabled by default and if it detects an environment of production, it will disable itself.

Module Settings

Each module can override some settings for cbsecurity according to its needs. You will create a cbsecurity struct within the module's settings struct in the ModuleConfig.cfc

module/ModuleConfig.cfc
settings = {
    // CB Security Module Settings
    cbsecurity : {
        // Module Relocation when an invalid access is detected, instead of each rule declaring one.
        "invalidAuthenticationEvent"  : "api:Home.onInvalidAuth",
        // Default Auhtentication Action: override or redirect when a user has not logged in
        "defaultAuthenticationAction" : "override",
        // Module override event when an invalid access is detected, instead of each rule declaring one.
        "invalidAuthorizationEvent"   : "api:Home.onInvalidAuthorization",
        // Default invalid action: override or redirect when an invalid access is detected, default is to redirect
        "defaultAuthorizationAction"  : "override",
        // The validator to use for this module
        "validator"                   : "JWTService@cbsecurity",
        // You can define your security rules here or externally via a source
        "rules"                       : [ { "secureList" : "api:Secure\.*" } ]
    }
}

The settings you see above are the only ones that module's support as of now.

Custom Firewalls

You can also register multiple instances of the cbsecurity module using different configurations by just adding them to your app's config or even your module's configuration. This will register a NEW firewall apart from the global security firewall registered using the global settings as defined above.

config/Coldbox.cfc
interceptors = [

    {
        class="cbsecurity.interceptors.Security",
        name="FirewallName",
        properties={
            // All Settings from above
        }

]

JWT Interceptions

The JWT Services will announce some key events for you to listen to

  • cbSecurity_onJWTCreation - Whenever a new token is generated for a user

  • cbSecurity_onJWTInvalidation - Whenever an invalidation occurs for a token

  • cbSecurity_onJWTValidAuthentication - Whenever a valid JWT token is parsed, tested and authenticated with the authentication services

  • cbSecurity_onJWTInvalidUser - When trying to find the token's subject and the user service returns null or not a valid user

  • cbSecurity_onJWTInvalidClaims - When the parsed token does not adhere to the required claims

  • cbSecurity_onJWTExpiration - When the parsed token has expired

  • cbSecurity_onJWTStorageRejection - When the parsed token is valid but cannot be found in the permanent storage

  • cbSecurity_onJWTValidParsing - When the parsed token has passed all validation procedures but has NOT been authenticated yet.

cbSecurity_onJWTCreation

This event has the following data in the interceptData struct

cbSecurity_onJWTInvalidation

This event has the following data in the interceptData struct

cbSecurity_onJWTValidAuthentication

This event has the following data in the interceptData struct

cbSecurity_onJWTInvalidUser

This event has the following data in the interceptData struct

cbSecurity_onJWTInvalidClaims

This event has the following data in the interceptData struct

cbSecurity_onJWTExpiration

This event has the following data in the interceptData struct

cbSecurity_onJWTStorageRejection

This event has the following data in the interceptData struct

cbSecurity_onJWTValidParsing

This event has the following data in the interceptData struct

Example

What's New With 2.11.x

2021-MAR-10

Added

  • Add a secureSameUser method to throw when passed a different user #29 ()

[2.11.1] => 2021-MAR-10

Fixed

  • Fix getRealIP() to only return originating user's source IP, if the forwarded ip is a list

Key

Description

token

The JWT token

payload

The payload that was used to create it

user

The user it belongs to

Key

Description

token

The JWT token that was invalidated

Key

Description

token

The JWT token that was parsed

payload

The payload that was decoded

user

The authenticated user

Key

Description

token

The JWT token that was parsed

payload

The JWT payload that was parsed

Key

Description

token

The JWT token that was parsed

payload

The JWT payload that was parsed

Key

Description

token

The JWT token that was parsed

payload

The JWT payload that was parsed

Key

Description

token

The JWT token that was parsed

payload

The JWT payload that was parsed

Key

Description

token

The JWT token that was parsed

payload

The JWT payload that was parsed

interceptors/SecurityAudit.cfc
component extends="coldbox.system.Interceptor"{

    function cbSecurity_onJWTCreation( event, interceptData ){
        // do what you like here
    }

}
https://github.com/coldbox-modules/cbsecurity/pull/29

What's New With 2.13.0

2021-SEP-

Added

  • Adobe 2021 Support

  • Migration to GitHub Actions from Travis CI

  • Refresh tokens support

  • Refresh token endpoint /cbsecurity/refreshToken for secure refresh token generation

  • Manual refresh token method on the JwtService : refreshToken( token )

  • Auto refresh token header interceptions for JWT validators

  • Detect on authenticate() if the payload is empty and throw the appropriate exceptions

  • Added ability for the authenticate( payload ) to receive a payload to authenticate

  • Added ability to recreate the token storage using a force argument getTokenStorage( force = false )

  • Ability for the parseToken() to choose to store and authenticate or just parse

Fixed

  • Unique jti could have collisions if tokens created at the same time, add randomness to it

  • TokenExpirationException not relayed from the base jwt library

  • If variables.settings.jwt.tokenStorage.enabled is disabled all invalidations failed, make sure if the storage is disabled to not throw storage exceptions.

What's New With 2.14.0

2021-OCT-07

Added

  • threadsafe annotation to all models to prevent invalid creations under load, since we don't use circular dependencies.

What's New With 2.8.0

2020-NOV-09

Added

  • parseToken( token ) now accepts a token of your choice to work with in the request or it will continue to discover it if not passed.

  • Added new JWT Service method: invalidateAll() which invalidates all tokens in the token storage

  • Added the new event: cbSecurity_onJWTInvalidateAllTokens that fires once all tokens in the storage are cleared

  • Added storage of the authenticated user into the prc scope when using attempt() to be consistent with API calls

Fixed

  • Spelling corrections on the readme

  • Added full var scoping for cbsecurity in JWTService calls

Release History

In this section you will find the release notes for each version we release under this major version. If you are looking for the release notes of previous major versions use the version switcher at the top left of this documentation book. Here is a breakdown of our major version releases.

Version 2.0

Version 2 is a major release of our security module. We completely refactored the engine to make it more modern and to adhere to our new coding standards. We then proceeded to enhance it to tap into our HMVC approach and allow rules to be contributed from modules themselves. We also added annotation driven security to complete the ability to secure not only incoming requests by rules but also by easy annotations.

We have made great strides in this release to make it a one-stop-shop for security concerns within ColdBox applications.

Version 1.0

Our first release as a module decoupled from the ColdBox 2 days!

Interception pointscbAuth

Custom Validator

Registration

In order to register your own custom security validator just open the config/Coldbox.cfc and add the validator key with the value being a WireBox ID that points to your object that will provide the validation.

Validator Interface

A security validator object is a simple CFC that implements the following functions

Each validator must return a struct with the following keys:

  • allow:boolean A Boolean indicator if authentication or authorization was violated

  • type:stringOf(authentication|authorization) A string that indicates the type of violation: authentication or authorization.

Example

Here is a sample validator using permission based security in both rules and annotation context

That's it! Go validate!

config/Coldbox.cfc
moduleSettings = {
    cbSecurity = {
         validator = "SecurityService"   
    }
}
cbsecurity/interfaces/IUserValidator.cfc
/**
 * Copyright since 2016 by Ortus Solutions, Corp
 * www.ortussolutions.com
 * ---
 * All security validators must implement the following methods
 */
interface{

	/**
	 * This function is called once an incoming event matches a security rule.
	 * You will receive the security rule that matched and an instance of the ColdBox controller.
	 *
	 * You must return a struct with two keys:
	 * - allow:boolean True, user can continue access, false, invalid access actions will ensue
	 * - type:string(authentication|authorization) The type of block that ocurred.  Either an authentication or an authorization issue.
	 *
	 * @return { allow:boolean, type:string(authentication|authorization) }
	 */
	struct function ruleValidator( required rule, required controller );

	/**
	 * This function is called once access to a handler/action is detected.
	 * You will receive the secured annotation value and an instance of the ColdBox Controller
	 *
	 * You must return a struct with two keys:
	 * - allow:boolean True, user can continue access, false, invalid access actions will ensue
	 * - type:string(authentication|authorization) The type of block that ocurred.  Either an authentication or an authorization issue.
	 *
	 * @return { allow:boolean, type:string(authentication|authorization) }
	 */
	struct function annotationValidator( required securedValue, required controller );

}
models/SecurityService.cfc
struct function ruleValidator( required rule, required controller ){
	return permissionValidator( rule.permissions, controller, rule );
}

struct function annotationValidator( required securedValue, required controller ){
	return permissionValidator( securedValue, controller );
}

private function permissionValidator( permissions, controller, rule ){
	var results = { "allow" : false, "type" : "authentication", "messages" : "" };
	var user 	= security.getCurrentUser();

	// First check if user has been authenticated.
	if( user.isLoaded() AND user.isLoggedIn() ){
		// Do we have the right permissions
		if( len( arguments.permissions ) ){
			results.allow 	= user.checkPermission( arguments.permission );
			results.type 	= "authorization";
		} else {
			results.allow = true;
		}
	}

	return results;
}

What's New With 2.6.0

2020-JUL-22

Added

  • New build layout based on new module layout

  • Auto github publishing release notes

  • More formatting goodness and watcher

Fixed

  • JWT Validator now passing permissions instead of roles

  • Token Storage checking was being done even if disabled

What's New With 2.1.0

2019-OCT-02

Small but big release!

  • Feature : cbauth upgraded to version 4

Authentication Services

ColdBox security can work with ANY authentication service provider.

You can register ANY authentication provider with cbsecurity by using the authenticationService setting. The value must be a valid WireBox Id and the object must adhere to the following interface. The authentication services can be used in conjunction with our JWT services and more features coming in the future.

// CB Security
cbSecurity : {
    
    // The WireBox ID of the authentication service to use in cbSecurity which must adhere to the cbsecurity.interfaces.IAuthService interface.
    "authenticationService" : "authenticationService@cbauth"
    
}

Please note that cbauth already implements this interface and is included with cbsecurity as a dependency.

If you are using cbauth as your authenticationService (the default), you also need to configure cbauth.

Authentication Service Interface

This interface has been provided by convenience and it is not mandatory at runtime (cbsecurity.interfaces.IAuthService)

cbsecurity.interfaces.IAuthService.cfc
/**
 * Copyright since 2016 by Ortus Solutions, Corp
 * www.ortussolutions.com
 * ---
 * If you register an authentication service with cbsecurity it must adhere to this interface
 */
interface{

    /**
     * Get the authenticated user
     *
	   * @throws NoUserLoggedIn : If the user is not logged in
	   *
     * @return User that implements IAuthUser
     */
    any function getUser();

    /**
     * Verifies if a user is logged in
     */
    boolean function isLoggedIn();

    /**
     * Try to authenticate a user into the system. If the authentication fails an exception is thrown, else the logged in user object is returned
     *
     * @username The username to log in with
     * @password The password to log in with
     *
     * @throws InvalidCredentials
	   *
	   * @return User : The logged in user object
     */
    any function authenticate( required username, required password );

    /**
  	 * Login a user into our persistent scopes
  	 *
  	 * @user The user object to log in
  	 *
  	 * @return The same user object so you can do functional goodness
	   */
    function login( required user );

    /**
     * Logs out the currently logged in user from the system
     */
    function logout();


}

You can find the information for cbauth in its own book:

If you are using cbauth as your authenticationService (the default), you also need to configure cbauth.

User Interface

As you can see from above, the authentication services all expect a User object to model your user in the system. So your User object must also adhere to the following methods modeled by the cbsecurity.interfaces.IAuthUser interface. This will allow the validators and JWT services to get the appropriate data it needs.

cbsecurity.interfaces.IAuthUser.cfc
interface{

    /**
     * Return the unique identifier for the user
     */
    function getId();

    /**
     * Verify if the user has one or more of the passed in permissions
     *
     * @permission One or a list of permissions to check for access
     *
     */
    boolean function hasPermission( required permission );

}

User Services

If you will be using cbauth or any of our JWT features, then we will also require you register a user service class that can provide us with the right data to encapsulate security using the userService setting. We have provided this interface for your usage:

cbsecurity.interfaces.IUserService.cfc
interface{

    /**
     * Verify if the incoming username/password are valid credentials.
     *
     * @username The username
     * @password The password
     */
    boolean function isValidCredentials( required username, required password );

    /**
     * Retrieve a user by username
     *
     * @return User that implements JWTSubject and/or IAuthUser
     */
    function retrieveUserByUsername( required username );

    /**
     * Retrieve a user by unique identifier
     *
     * @id The unique identifier
     *
     * @return User that implements JWTSubject and/or IAuthUser
     */
    function retrieveUserById( required id );
}

If using cbauth, you also have to specify the UserServiceClass key in the cbauth module settings.

Remember that the User Service setting is only required if you will be using JWT token security

Security Rules

We have seen how the module works on the concept of rules and how to declare them. Before we dig deeper and decompose the rules let's have a look at the processing of the rules first.

Rule Anatomy

Each rule is modeled by a struct with keys in it:

The only required key is the secureList which is what you are trying to secure. The rest are optional and described below. Please note that you can add as many keys as you like to your security rules, which can contain much more context and information for the validators to use for validation.

Please remember that by default the secure and white lists are evaluated as regular expressions. You can turn that off in your

Rules processing

When processing rules, it is important to realize these rules come as an array which will be processed in order, so make sure your more specific rules will be processed before the more generic ones.

Rule Elements

Rule Overrides

As we saw from the overview and our configuration sections. We can declare the default actions for authorizations and authentication issues and to which events/URLs to go if that happens. There can be a time where you can override those global/module settings directly within a rule. Let's explore those overrides:

Redirect

If you add a redirect element, then you will be explicitly overriding the global/module setting and if a match is made a redirect will occur.

OverrideEvent

If you add an overrideEvent element, then you will be explicitly overriding the global/module setting and an event override will occur.

Action

If you add a action element, then you will be explicitly overriding the global/module setting and the action will be based on this value (override or event)

White Lists

If a rule has a white list, then it means that you can declare what are the exceptions to ALLOW if the incoming URL/event was matched against the securedList. This is a great way to say, hey secure all but allow the following events:

Please note: if a rule has a whiteList, it only applies to the current rule. So if the whitelist matches, it the current rule is skipped and the process continues to the next rule.

Sometimes you want to make sure ALL events are secured, except for the ones specified, such as login events. If you add new functionality to your app it is easy to forget a new rule. To prevent unwanted access you could specify a LAST rule, which matches ALL event but NO permission at all. In that case you have to add a whitelist for all events which should still pass, for example:

Refresh Tokens

ColdBox Security supports the concept of refresh tokens alongside the normal JWT access tokens. Let's start exploring this feature in detail.

What Is a Refresh Token?

A refresh token is a credential artifact that lets a client application get new access tokens without having to ask the user to log in again. Access tokens may be valid for a short amount of time. Once they expire, client applications can use a refresh token to "refresh" the access token.

The client application can get a new access token as long as the refresh token is valid and unexpired. Consequently, a refresh token that has a very long lifespan could theoretically give infinite power to the token bearer to get a new access token to access protected resources anytime. The bearer of the refresh token could be a legitimate user or a malicious user.

Refresh Token Configuration

In the jwt section of the cbsecurity configuration you will have the following settings dealing with refresh tokens (Please note that the other jwt configurations are also mandatory)

jwt : {

    ...

    // If true, enables refresh tokens, token creation methods will return a struct instead of just an access token string
    // e.g. { access_token: "", refresh_token : "" }
    "enableRefreshTokens"   : false,
    // The default expiration for refresh tokens in minutes, defaults to 7 days
    "refreshExpiration"     : 10080,
    // The custom header to inspect for refresh tokens
    "customRefreshHeader"    : "x-refresh-token",
    // If enabled, the JWT validator will inspect the request for refresh tokens and expired access tokens
    // It will then automatically refresh them for you and return them back as 
    // response headers in the same request according to the `customRefreshHeader` and `customAuthHeader`
    "enableAutoRefreshValidator" : false,
    // Enable the POST > /cbsecurity/refreshtoken API endpoint
    "enableRefreshEndpoint" : false
}

EnableRefreshTokens

This setting is used to turn on the refresh capabilities of the JWT Service. If this remains false, then exceptions will be thrown when trying to use refresh capabilities.

RefreshExpiration

The default time refresh tokens expire in. The default is 7 days or 10080 minutes

CustomRefreshHeader

The header to inspect for refresh tokens for automatic refreshment or our refresh endpoints. The default is x-refresh-token

EnableAutoRefreshValidator

If you enable the auto refresh validator setting, then cbsecurity will try to auto-refresh expired access tokens via the Validator security events. These events fire when a rule is detected or a secured annotation is detected.

EnableRefreshEndpoint

If enabled, the REST cbsecurity/refreshToken endpoint will be available for the application, so users can refresh their tokens.

Token Creation

You must enable the setting (enableRefreshTokens) in order for the following methods to return a struct of tokens. If not, only the access token will be returned as a string.

  • attempt( username, password ):struct

  • fromUser( user, customClaims ):struct

The returned struct will contain the access and refresh tokens:

{
    "access_token"  : "AYjcyMzY3ZDhiNmJk",
    "refresh_token" : "RjY2NjM5NzA2OWJj"
}

This is the same procedure for creating access tokens, but now you get a struct of tokens instead of a single access token.

Refreshing Tokens Manually

You can refresh tokens manually by using the refreshToken( token, customClaims ) method on the JwtService object. You can pass a valid refresh token to be used for refreshment or pass none and the token will be inspected from the headers or incoming rc using the x-refresh-token value or whatever you setup as your customRefreshHeader setting.

var newTokens = jwtService.refreshToken();

var newTokens = jwtService.refreshToken( storedRefreshToken );

Here is the signature for the refresh method:

/**
 * Manually refresh tokens by passing a valid refresh token and returning two new tokens:
 * <code>{ access_token : "", refresh_token : "" }</code>
 *
 * @refreshToken A refresh token
 * @customClaims A struct of custom claims to apply to the new tokens
 *
 * @throws RefreshTokensNotActive If the setting enableRefreshTokens is false
 * @throws TokenExpiredException If the token has expired or no longer in the storage (invalidated)
 * @throws TokenInvalidException If the token doesn't verify decoding
 * @throws TokenNotFoundException If the token cannot be found in the headers
 *
 * @return A struct of { access_token : "", refresh_token : "" }
 */
struct function refreshToken( token = discoverRefreshToken(), struct customClaims = {} )

customClaims where added in v2.15.0

Important:

Please note that the currently used refresh token will be invalidated and rotated for you automatically. This is a security feature called refresh token rotation, where the refreshed token is automatically rotated for you upon refresh usage.

Refresh Token Endpoint

If you have enabled the refresh token setting (enableRefreshEndpoint) then you will also have access to the POST > /cbsecurity/refreshtoken API endpoint.

This endpoint is used for applications to refresh their access tokens using the refresh token received when authenticating in the application.

This endpoint must be executed with a POST and you will need to pass in your refresh token via the following header: x-refresh-token or rc form variable of the same name. If valid, the response data will contain two new tokens for you:

{
    "access_token" : "AYjcyMzY3ZDhiNmJk",
    "refresh_token" : "RjY2NjM5NzA2OWJj"
}

Important:

Please note that the currently used refresh token will be invalidated and rotated for you automatically. This is a security feature called refresh token rotation, where the refreshed token is automatically rotated for you upon refresh usage.

Refresh Token Rotation

CBSecurity by default provides you with refresh token rotation every time you want to refresh your access token. This guarantees that every time an application exchanges a refresh token for an access token, a NEW refresh token is returned as well. The old refresh token is invalidated and can no longer be used.

Therefore, you no longer have a long-lived refresh token that could provide illegitimate access to resources if it ever becomes compromised or leaked. The threat of unauthorized access is reduced as refresh tokens are continually exchanged and invalidated.

Refresh Token Header Auto Refreshment

CBSecurity has the ability to refresh access tokens automatically for you when calling any secure resource that is protected by the JWT Validator. All you have to do is send in both tokens via the appropriate headers and enable the autoRefreshValidator setting:

  • access token

    • bearer token or

    • x-auth-token

  • refresh token

    • x-refresh-token

If the access token has expired or is invalid or missing and the x-refresh-token was passed and is valid, then the access token will be re-generated, the refresh token will be rotated, the request will continue as normal and two new response headers will be sent back to the calling application.

  • x-auth-token : refreshed access token

  • x-refresh-token : new refresh token

The calling application can monitor if those two response headers are sent and save them appropriately.

{
    "whitelist"     : "", 
    "securelist"    : "", 
    "match"            : "event",  // or url
    "roles"            : "", 
    "permissions"    : "", 
    "redirect"         : "", 
    "overrideEvent"    : "", 
    "useSSL"        : false, 
    "action"        : "redirect", // or override 
    "module"        : ""
};

Property

Type

Description

match

event or URL

Determines if it needs to match the incoming URL or the incoming event. By default it matches the incoming event.

whitelist

varchar

A comma delimited list of events or regex patterns to whitelist or to bypass security on if a match is made on the secureList

securelist

varchar

A comma delimited list of events or regex patterns to secure

roles

varchar

A comma delimited list of roles that can access these secure events

permissions

varchar

A comma delimited list of permissions that can access these secure events

redirect

varchar

An event or route to redirect if the user is not authenticated or authorized

overrideEvent

varchar

The event to override using ColdBox's event.overrideEvent() if the user if not authenticated or authorized

useSSL

Boolean

If true, force SSL, else use whatever the request protocol is

action

string

The action to use (redirect or override) when no explicit overrideEvent or redirect elements are defined. If not set, then we use the global settings.

{
    "secureList" : "*",
    "redirect" : "mysecret.event"
}
{
    "secureList" : "*",
    "overrideEvent" : "main.onInvalidEvent"
}
{
    "secureList" : "^api.*",
    "action" : "override"
}
{
    "secureList" : ".*",
    "whitelist : "^login"
}
{
    "secureList" : ".*",
    "whitelist" : "login",
    "permissions" : "nonExistingPermission"
}
configuration settings.
cbsecurity rules processing

Model Rules

If you prefer to store your rules your way, then that's perfectly fine. Just make your rules setting point to model and then provide us with the object to get the rules from.

Property

Type

Required

Default

Description

rulesModel

string

true

---

The WireBox ID of the object that we will use to retrieve the rules from

rulesModelMethod

string

false

getSecurityRules

The name of the method to call on the object.

config/Coldbox.cfc
moduleSettings = {
	// CB Security
	cbSecurity : {
		"rules" 		: "model",
		"rulesModel" 	: "SecurityService"
	}
};

Logo

What's New With 2.10.0

2021-FEB-12

Added

  • Moved the registration of the validator from the configure() to the afterAspectsLoad() interception point to allow for modules to declare the validator if needed.

  • Moved handler bean to afterAspectsLoad() to allow for module based invalid events to work.

What's New With 2.15.0

2021-DEC-10

🚀 Added

  • Pass custom claims from refreshToken() method when refreshing tokens

In the JWTService the refreshToken( struct customClaims = {} ) now has a customClaims argument which you can use to seed the refresh token with custom claims.

  • Pass in the current JWT payload in to getJWTCustomClaims( payload ) method

This was done to help authors have the exact payload that was used for the execution call. This affects the IJwtSubject interface.

  • The auto refresh token features now will auto refresh not only on expired tokens, but on invalid and missing tokens as well. Thanks to @elpete

🐛 Fixed

  • Timeout in token storage is now the token timeout

Rule Sources

The security firewall can be configured with rules that can come from many different sources:

  • Declared inline in your config/Coldbox.cfc

  • A JSON file

  • An XML file

  • From a model object via a method call

  • From a database

  • Declared inline in ANY module's ModuleConfig.cfc

When defining your rules source, you ALWAYS have to define the rules property. You specify an array of rules for inline, or rules = "(db|json|xml|model)" if you define your rules externally. If you have external rules you probably have to specify additional properties as explained in the next pages.

Let's start exploring these sources.

JWT Services

CBSecurity also provides you with a JWT (Json Web Tokens) authentication and authorization system.

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.

Signed tokens can verify the integrity of the claims contained within it, while encrypted tokens hide those claims from other parties. When tokens are signed using public/private key pairs, the signature also certifies that only the party holding the private key is the one that signed it.

You can find much more information about JWT at jwt.io.

When should you use JSON Web Tokens?

JSON Web Tokens have become the standard for authenticating and authorizing API requests. They can be used on their own or with an oauth/single sign-on server as well.

  • Authorization: This is the most common scenario for using JWT. Once the user is logged in, each subsequent request will include the JWT, allowing the user to access routes, services, and resources that are permitted with that token.

  • Information Exchange: JSON Web Tokens are a good way of securely transmitting information between parties. Because JWTs can be signed—for example, using public/private key pairs—you can be sure the senders are who they say they are. Additionally, as the signature is calculated using the header and the payload, you can also verify that the content hasn't been tampered with.

The ColdBox Security module will assist you with all the generation, decoding, encoding and security aspects of JWT. All you need to do is, configure it, create a few standard files and off you go.

Tokens

The tokens created by the JWT services will have the mandatory headers, but also will have a standardizes payload structure. This payload structure can also be customized as you see fit.

A JSON Web Token encodes a series of claims in a JSON object. Some of these claims have specific meaning, while others are left to be interpreted by the users. You can consider claims to be the keys of the payload structure and it can contain, well, pretty much anything you like.

Base Claims

Here are the base claims that the ColdBox Security JWT token creates for you automatically:

  • Issuer (iss) - The issuer of the token (defaults to the application's base URL)

  • Issued At (iat) - When the token was issued (unix timestamp)

  • Subject (sub) - This holds the identifier for the token (defaults to user id)

  • Expiration time (exp) - The token expiry date (unix timestamp)

  • Unique ID (jti) - A unique identifier for the token (md5 of the sub and iat claims)

  • Scopes (scope) - A space delimited string of scopes attached to the token

  • Refresh Token (cbsecurity_refresh ) - If you are using refresh tokens, this custom claim will be added to the payload.

mytoken.json
{
  "iat": 1569340662,
  "scope": "",
  "iss": "http://127.0.0.1:56596/",
  "sub": 123,
  "exp": 1569344262,
  "jti": "12954F907C0535ABE97F761829C6BD11"
}
myRefreshToken.json
{
  "iat": 1569340662,
  "scope": "",
  "iss": "http://127.0.0.1:56596/",
  "sub": 2222,
  "exp": 1569344262,
  "jti": "234234CDDEEDD",
  "cbsecurity_refresh" : true
}

You can add much more to this payload via the JWT service methods or via the User that models the token.

Our JwtService

The service can be found here cbsecurity.models.JWTService and can be retrieved by either injecting the service (JwtService@cbsecurity) or using our helper method (jwtAuth()).

// Injection
property name="jwtService" inject="JwtService@cbsecurity";

// Helper Method in any handler/layout/interceptors/views
jwtAuth()

In order to begin exploring the JWT capabilities, let's explore how to configure it first.

Configuration

Our JWT services have several configuration settings, let's explore them:

cbsecurity : {
    // The WireBox ID of the authentication service to use in cbSecurity which must adhere to the cbsecurity.interfaces.IAuthService interface.
    authenticationService  : "authenticationService@cbauth",
    // WireBox ID of the user service to use
    userService             : "",
    // The name of the variable to use to store an authenticated user in prc scope if using a validator that supports it.
    prcUserVariable         : "oCurrentUser",
    // JWT Settings
    jwt                     : {
        // The issuer authority for the tokens, placed in the `iss` claim
        issuer                          : "",
        // The jwt secret encoding key, defaults to getSystemEnv( "JWT_SECRET", "" )
        // This key is only effective within the `config/Coldbox.cfc`. Specifying within a module does nothing.
        secretKey               : getSystemSetting( "JWT_SECRET", "" ),
        // by default it uses the authorization bearer header, but you can also pass a custom one as well.
        customAuthHeader        : "x-auth-token",
        // The expiration in minutes for the jwt tokens
        expiration              : 60, 
        // If true, enables refresh tokens, token creation methods will return a struct instead
        // of just the access token. e.g. { access_token: "", refresh_token : "" }
        enableRefreshTokens        : false,
        // The default expiration for refresh tokens, defaults to 30 days
        refreshExpiration          : 10080,
        // The Custom header to inspect for refresh tokens
        customRefreshHeader        : "x-refresh-token",
        // If enabled, the JWT validator will inspect the request for refresh tokens and expired access tokens
        // It will then automatically refresh them for you and return them back as
        // response headers in the same request according to the customRefreshHeader and customAuthHeader
        enableAutoRefreshValidator : false,
        // Enable the POST > /cbsecurity/refreshtoken API endpoint
        enableRefreshEndpoint      : true,
        // encryption algorithm to use, valid algorithms are: HS256, HS384, and HS512
        algorithm               : "HS512",
        // Which claims neds to be present on the jwt token or `TokenInvalidException` upon verification and decoding
        requiredClaims          : [] ,
        // The token storage settings
        tokenStorage            : {
            // enable or not, default is true
            "enabled"       : true
            // A cache key prefix to use when storing the tokens
            "keyPrefix"     : "cbjwt_", 
            // The driver to use: db, cachebox or a WireBox ID
            "driver"        : "cachebox",
            // Driver specific properties
            "properties"    : {
                cacheName : "default"
            }
        }
    }
}

Authentication Service

The WireBox Id of the service to provide our authentication. cbauth is our default provider, but you can use any authentication service that adheres to our interface.

User Service

The WireBox Id of the service to provide our user retrieval and validation functions. You can use any service that adheres to our interface.

prcUserVariable

The default variable name in the prc scope that will be used to store an authenticated user object if the JWT request is valid. The default is prc.oCurrentUser

issuer

The issuer authority for the tokens, placed in the iss claim of the token. If empty, we will use the event.buildLink() to create the issuer. By default, our validators also check that tokens are created by the same issuer.

secretKey

The secret key is used to sign the JWT tokens. By default it will try to load an environment variable called JWT_SECRET , if that setting is also empty, then we will auto-generate a secret token that will last as long as the ColdFusion application scope lasts. So technically, your secret will rotate only if a secret is not specified.

Also, this key is ignored in modules. To specify a fixed key to be used in your modules, you will have to configure it by adding a cbsecurity key settings in the moduleSettings structure within the config/Coldbox.cfc.

Your secret key will auto-rotate every application scope rotation. Please note that all tokens used after that scope rotation will automatically become invalid.

Please note that we use the jwt-cfml library for encoding/decoding tokens. Please refer to it's documentation in order to leverage RS and ES algorithms with certificates.

https://forgebox.io/view/jwt-cfml

customAuthHeader

By default, our jwt services will look into the authorization header for a bearer token. However, it can also look in a custom header by this name, which defaults to x-auth-token. Finally, if not found, it will also look into the rc scope for a rc[ 'x-auth-token' ] as well.

Expiration

The default expiration in minutes for the JWT tokens. Defaults to 60 minutes

Algorithm

The encryption algorithm to use for the tokens. The default is HS512, but the available ones for are:

  • HS256

  • HS384

  • HS512

  • RS256

  • RS384

  • RS512

  • ES256

  • ES384

  • ES512

In the case of the RS and ES algorithms, asymmetric keys are expected to be provided in unencrypted PEM or JWK format (in the latter case first deserialize the JWK to a CFML struct). When using PEM, private keys need to be encoded in PKCS#8 format.

If your private key is not currently in this format, conversion should be straightforward:

$ openssl pkcs8 -topk8 -nocrypt -in privatekey.pem -out privatekey.pk8

When decoding tokens, either a public key or certificate can be provided. (If a certificate is provided, the public key will be extracted from it.)

RequiredClaims

This is an array of claim names that each token MUST have in order to be authenticated. If a token comes in but does not have these claims in the payload structure, it will be deemed invalid.

Token Storage

By default, our JWT services will store tokens in CacheBox for you in order to be able to invalidate them. We ship with two providers for token storage: db and cachebox.

Enabled

By default the token storage is enabled.

KeyPrefix

The key prefix to use when storing the keys in the permanent storage. Defaults to cbjwt_

Driver

The driver to use. Can be either db or cachebox or your own WireBox Id for using a custom storage.

Properties

A struct of properties to configure each storage with.

Refresh Token Configuration

Refresh tokens have several configuration items, check them out in our refresh token configuration section.

JWT Subject Interface

The next step is to make sure that our JWT services can handle the construction of the JWT tokens as per YOUR requirements. So your User object must implement our JWTSubject interface with the following functions:

cbsecurity.interfaces.jwt.IJwtSubject.cfc
/**
 * Copyright since 2016 by Ortus Solutions, Corp
 * www.ortussolutions.com
 * ---
 * If you use the jwt services, then your jwt subject user must implement this interface
 */
interface{

    /**
     * A struct of custom claims to add to the JWT token when creating it
	 *
	 * @payload The actual payload structure that was used in the request
	 *
	 * @return A structure of custom claims
     */
    struct function getJwtCustomClaims( required struct payload );

    /**
     * This function returns an array of all the scopes that should be attached to the JWT token that will be used for authorization.
     */
    array function getJwtScopes();

}

Basically, it's two functions:

  • getJwtCustomClaims( payload ) - This is a struct of custom claims to incorporate into the token payload at construction time. This can be ANYTHING you like.

  • getJwtScopes() - We will also call this at construction time in order to incorporate the right permission scopes into the token according to your user. This must be an array of scopes/permissions.

Since also the authentication services will be used with JWT, your user object might end up looking like this:

models/User.cfc
component accessors="true" {

    property name="auth" inject="authenticationService@cbauth";

    property name="id";
    property name="firstName";
    property name="lastName";
    property name="username";
    property name="password";

    function init(){
        variables.id        = "";
        variables.firstName = "";
        variables.lastName  = "";
        variables.username  = "";
        variables.password  = "";

        variables.permissions = [ "write", "read" ];

        return this;
    }

    boolean function isLoaded(){
        return ( !isNull( variables.id ) && len( variables.id ) );
    }

    /**
     * A struct of custom claims to add to the JWT token
     */
    struct function getJWTCustomClaims(){
        return { "role" : "admin" };
    }

    /**
     * This function returns an array of all the scopes that should be attached to the JWT token that will be used for authorization.
     */
    array function getJWTScopes(){
        return variables.permissions;
    }

    /**
     * Verify if the user has one or more of the passed in permissions
     *
     * @permission One or a list of permissions to check for access
     *
     */
    boolean function hasPermission( required permission ){
        if ( isSimpleValue( arguments.permission ) ) {
            arguments.permission = listToArray( arguments.permission );
        }

        return arguments.permission
            .filter( function(item){
                return ( variables.permissions.ListFindNoCase( item ) );
            } )
            .len();
    }

}

Authentication and User Services

Please note that the JWT validators must talk to the authentication and user services. Please refer to the Authentication Services page to configure and create them.

JWT Methods

Ok, now we can focus on all the wonderful methods the JWT service offers:

Token Creation Methods

  • attempt( username, password, [ customClaims:struct ] ):token - Attempt to authenticate a user with the authentication service and if successful, return the token using the identifier and custom claims. Exception if invalid authentication

  • fromUser( user, [ customClaims:struct ] ):token - Generate a token according to the passed user object and custom claims.

Raw JWT Methods

  • encode( struct payload ):token - Generate a raw jwt token from a native payload struct.

  • verify( required token ):boolean - Verify a token string or throws exception

  • decode( required token ):struct - Decode and retrieve the passed in token to CFML struct

Parsing and Helper Methods

  • parseToken( token, storeInContext, authenticate ):struct - Get the decoded token using the headers strategy and store it in the prc.jwt_token and the decoded data as prc.jwt_payload if it verifies correctly. Throws: TokenExpiredException if the token is expired, TokenInvalidException if the token doesn't verify decoding, TokenNotFoundException if not found

  • getToken():string - Get the stored token from prc.jwt_token, if it doesn't exist, it tries to parse it via parseToken(), if not token is set this will be an empty string.

  • getPayload():struct - Get the stored token from prc.jwt_payload, if it doesn't exist, it tries to parse it via parseToken(), if not token is set this will be an empty struct.

  • setToken( token ):JWTService - Store the token in prc.jwt_token, and store the decoded version in prc.jwt_payload

Authentication Helpers

  • authenticate( [payload] ):User - Authenticates a passed or detected token payload and return the user it represents

  • getuser() - Get the authenticated user according to the access token detected

  • logout() - Logout a user and invalidate their token

Storage Methods

  • invalidateAll( async:false ) - Invalidate all access and refresh tokens in permanent storage

  • invalidate( token ) - Invalidates the incoming token by removing it from the permanent storage.

  • isTokenInStorage( token ) - Checks if the passed token exists in permanent storage.

  • getTokenStorage( force:false ) - Get the current token storage implementation. You can also force create it again if needed.

Refresh Methods

  • attempt( username, password, [ customClaims:struct ] ):struct - Attempt to authenticate a user with the authentication service and if successful, return a struct containing an access and refresh token.

  • fromUser( user, [ customClaims:struct ] ):struct - Generate a struct of refresh and access token according to the passed user object and custom claims.

{
    "access_token"  : "AYjcyMzY3ZDhiNmJk",
    "refresh_token" : "RjY2NjM5NzA2OWJj"
}

Putting it Together

That's it, we are ready to put it all together. Now cbsecurity knows about your authentication/user services, can talk to your user to create tokens and can guard the incoming requests via the JWT Validator. Here is a sample controller for login, logout and user registration:

Let's configure some routes first:

post( "/api/login" , "api.auth.login" );
post( "/api/logout" , "api.auth.logout" );
post( "/api/register" , "api.auth.register" );

Then build out the Auth controller

component{

    function login( event, rc, prc ){
        param rc.username = "";
        param rc.password = "";

        try {
            var token = jwtAuth().attempt( rc.username, rc.password );
            return {
                "error"   : false,
                "data"    : token,
                "message" : "Bearer token created and it expires in #jwtAuth().getSettings().jwt.expiration# minutes"
            };
        } catch ( "InvalidCredentials" e ) {
            event.setHTTPHeader( statusCode = 401, statusText = "Unauthorized" );
            return { "error" : true, "data" : "", "message" : "Invalid Credentials" };
        }
    }

    function register( event, rc, prc ){
        param rc.firstName = "";
        param rc.lastName  = "";
        param rc.username  = "";
        param rc.password  = "";

        prc.oUser = populateModel( "User" );
        userService.create( prc.oUser );

        var token = jwtAuth().fromuser( prc.oUser );
        return {
            "error"   : false,
            "data"    : token,
            "message" : "User registered correctly and Bearer token created and it expires in #jwtAuth().getSettings().jwt.expiration# minutes"
        };
    }

    function logout( event, rc, prc ){
        jwtAuth().logout();
        return { "error" : false, "data" : "", "message" : "Successfully logged out" };
    }
}

Make sure you add validation!

That's it, we now can login a user, give them a token, register a new user and give them their token, and also log them out. The next step is for you to build your rules and/or security annotations and make sure the JWT validator is configured for your global app or module.

Web Server Configuration

In order to implement JWT authentication in your application, you may need to modify some web server settings. Most web servers have default content length restrictions on the size of an individual header. If your web server platform has such default enabled, you will need to increase the buffer size to accommodate the presence of JTW tokens in both the request and response headers. The size of a JWT token header, encrypted via the default cbSecurity HMAC512 algorithm, is around 44 kilobytes. As such you will need to allow for at least that size. Below are some examples for common web server configurations

NGINX

The following configuration may be applied to the main NGINX http configuration block to allow for the presence of tokens in both the request and response headers:

http {
    # These settings affect outbound headers via proxy server
    proxy_buffer_size   64k;
    proxy_buffers   4 128k;
    proxy_busy_buffers_size   128k;
    # These settings affect the http client request headers
    client_body_buffer_size     128k;
    client_header_buffer_size   64k;
    large_client_header_buffers 8 128k;
    # These settings affect HTTP/2 headers, however some versions of NGINX will throw an error on HTTP/1 requests if these are not present
    http2_max_header_size 128k;
    http2_max_field_size 1000m;
}

IIS

You will need to modify two registry keys:

  1. HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\HTTP\Parameters\MaxFieldLength - Sets an upper limit, in bytes, for each header. The default value is 65534 bytes and the maximum value is 65534 bytes ( 64kb )

  2. HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\HTTP\Parameters\MaxRequestBytes - Sets the upper limit for the request line and the headers, combined. As such 128K should allow for both long URLs, as well as JWT tokens in the headers. The default value is 16384 bytes and the maximum value is 16777216 bytes ( 16 MB )

Apache

You will need to add a LimitRequestFieldSize setting in each <VirtualHost...> entry in order increase the default header size from the default 8 kilobytes. Example, with a setting of 128 kilobytes:

<VirtualHost 10.10.50.50:80>
    ServerName www.mysite.com

    LimitRequestFieldSize 128000

    RewriteEngine On
    ...
    ...
</VirtualHost>

CFML Security Validator

ColdBox security has had this security validator since version 1, in which it will talk to the ColdFusion engine's security methods to authenticate and authorize users. With it you will be able to authenticate users and also do role base authorization.

All you need to do is use the WireBox ID of CFValidator@cbsecurity in your validator setting:

cbsecurity = {

    validator = "CFValidator@cbsecurity"

}

The default value is of CFValidator@cbsecurity which is the WireBox ID for the object.

The code for this validator can be found at cbsecurity.models.CFValidator

ColdFusion Security Functions

A container for user authentication and login code. The body of the tag runs only if the user is not logged in. When using application-based security, you place code in the body of the cflogin tag to check the user-provided ID and password against a data source, LDAP directory, or other repository of login identification. The body of the tag includes a cfloginuser tag (or a ColdFusion page that contains a cfloginuser tag) to establish the authenticated user's identity in ColdFusion.

Identifies (logs in) a user to ColdFusion. Specifies the user's ID, password, and roles. This tag is typically used inside a cflogin tag. The cfloginuser tag requires three attributes, name, password, and roles, and does not have a body. The roles attribute is a comma-delimited list of role identifiers to which the logged-in user belongs. All spaces in the list are treated as part of the role names, so you should not follow commas with spaces.While the user is logged-in to ColdFusion, security functions access the user ID and role information.

Logs out the current user. Removes knowledge of the user ID and roles from the server. If you do not use this tag, the user is automatically logged out as described in Logging out users in .The cflogout tag does not take any attributes, and does not have a body.

Authenticates a user name and password against the NT domain on which ColdFusion server is running, and optionally retrieves the user's groups.

If you include a roles attribute, the function executes only when there is a logged-in user who belongs to one of the specified roles.

Returns True if the current user is a member of the specified role.

Returns the ID of the currently logged-in user.This tag first checks for a login made with cfloginuser tag. If none exists, it checks for a web server login (cgi.remote_user.

Example:

handlers/security.cfc
component{

	function login( event, rc, prc ){
		event.setView( "security/login" );
	}
	
	function doLogin( event, rc, prc ){
		cflogin(
			idletimeout=getSetting( "LoginTimeout" ), 
			applicationtoken=getSetting( "AppName" ), 
			cookiedomain='myapp.com'
		){
			cfoauth(
				type        = "Google",
				clientid    = "YOUR_CLIENT_ID",
				secretkey   = "YOUR_GOOGLE_CLIENTSECRET",
				redirecturi = "YOUR_CALLBACK_URI",
				result      = "res",
				scope       = "YOUR_SCOPES",
				state       = "cftoken=#cftoken#"
			);

			cfloginuser(
				name     = "#res.other.email#", 
				password = "#res.access_token#", 
				roles    = "user"
			);
		}
	}
	 
	function doLogout( event, rc, prc ){
	   
	    cflogout();
	 	relocate( "security.login" );
	}

}

For more information about cflogin, cfloginuser and cflogout, please visit the docs http://cfdocs.org/security-functions

ScopesAuth0 Docs

Inline Rules

Inline rules will be used by declaring them in your configuration for cbsecurity in the config/ColdBox.cfc. This is done by making the rules key an array of rule structures.

config/Coldbox.cfc
moduleSettings = {
	// CB Security
	cbSecurity : {
		// The global security rules
		"rules" : [
			// should use direct action and do a global redirect
			{
				"whitelist": "",
				"securelist": "admin",
				"match": "event",
				"roles": "admin",
				"permissions": "",
				"action" : "redirect"
			},
			// no action, use global default action
			{
				"whitelist": "",
				"securelist": "noAction",
				"match": "url",
				"roles": "admin",
				"permissions": ""
			},
			// Using overrideEvent only, so use an explicit override
			{
				"securelist": "ruleActionOverride",
				"match": "url",
				"overrideEvent": "main.login"
			},
			// direct action, use global override
			{
				"whitelist": "",
				"securelist": "override",
				"match": "url",
				"roles": "",
				"permissions": "",
				"action" : "override"
			},
			// Using redirect only, so use an explicit redirect
			{
				"securelist": "ruleActionRedirect",
				"match": "url",
				"redirect": "main.login"
			}
		]
	}
};
cflogin
cfloginuser
cflogout
Using ColdFusion security tags and functions
cfNTauthenticate
cffunction
IsUserInAnyRole
GetAuthUser

Overview

In this page you will find a thorough overview of the capabilities of the ColdBox Security module.

Authentication/Authorization

For any security system you need to know who is authenticated (authentication) and what (authorization) this user is allowed to do. cbsecurity is no different, so it provides an:

  • Authentication system which performs the following functions:

    • Validates user credentials

    • Logs them in and out

    • Tracks their security in sessions or any custom storage

  • Authorization system which:

    • validates permissions or roles or both

ColdBox Security Firewall

With the ColdBox security module you will be able to secure all your incoming ColdBox events from execution either through security rules or discrete annotations within your code. You will also be able to leverage our CBSecurity service model to secure any code context anywhere.

ColdBox Security Firewall

The module wraps itself around the preProcess interception point (The first execution of a ColdBox request) and will try to validate if the request has been authenticated and authorized to execute. This is done via security rules and/or annotations on the requested handler actions through a CBSecurity Validator . The job of the validator is to make sure user requests have been authenticated and authorized:

  • CBAuth Validator: this is the default (and recommended) validator, which makes use of the cbauth module. It provides authentication and permission based security.

  • CFML Security Validator: Coldbox security has had this validator since version 1, and it will talk to the ColdFusion engine's security methods (cflogin,cflogout). It provides authentication and roles based security.

  • JWT Validator: If you want to use Json Web Tokens the JWT Validator provides authorization and authentication by validating incoming access/refresh tokens for RESTFul APIs.

  • Custom Validator: You can define your own authentication and authorization engines and plug them in to the cbsecurity framework.

How Does Validation Happen?

How does the interceptor know a user doesn't or does have access? Well, here is where you register a Validator CFC (validator setting) with the interceptor that implements two validation functions: ruleValidator() and annotationValidator() that will allow the module to know if the user is logged in and has the right authorizations to continue with the execution.

You can find an interface for these methods in cbsecurity.interfaces.ISecurityValidator

The validator has two options to determine if the user will be allowed access:

  • The ruleValidator() function will evaluate configured security rules

  • The annotationValidator() function will look at security annotations in your handler and handler actions.

You can use rules, annotations or even both. Rules are much more flexible, but more complex. Rules will be evaluated before annotations.

The validators' job is to tell back to the firewall if they are allowed access and if they don't, what type of validation they broke: 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 action.

Validation Process

Once the firewall has the results and the user is NOT allowed access, the following will occur:

  • The request that was blocked will be logged via LogBox with the offending IP and extra metadata

  • The current requested URL will be flashed as _securedURL so it can be used in relocations

  • If using a rule, the rule will be stored in prc as cbsecurity_matchedRule

  • The validator results will be stored in prc as cbsecurity_validatorResults

  • If the type of invalidation is authentication the cbSecurity_onInvalidAuthentication interception will be announced

  • If the type of invalidation is authorization the cbSecurity_onInvalidAuthorization interception will be announced

  • If the type is authentication the default action (defaultAuthenticationAction) for that type will be executed (An override or a relocation) will occur against the setting invalidAuthenticationEvent which can be an event or a destination URL.

  • If the type is authorization the default action (defaultAuthorizationAction) for that type will be executed (An override or a relocation) invalidAuthorizationEvent which can be an event or a destination URL.

Security Rules vs Annotation Security

Security Rules
{
    "whitelist"     : "", 
    "securelist"    : "", 
    "match"            : "event",  // or url
    "roles"            : "", 
    "permissions"    : "", 
    "redirect"         : "", 
    "overrideEvent"    : "", 
    "useSSL"        : false, 
    "action"        : "redirect", // or override 
    "module"        : ""
};
Annotations
// Secure the entire handler
component secured{

	function index(event,rc,prc){}
	function list(event,rc,prc){}

}
// Same as this
component secured=true{
}

// Do NOT secure the handler
component secured=false{
}
// Same as this, no annotation!
component{

	function index(event,rc,prc) secured{
	}

	function list(event,rc,prc) secured="list"{

	}
	 

Your application can be secured with security rules or handler and method annotations. Before making your choice, you should take the following arguments into consideration:

  • annotations are directly visible in your code, but very static.

  • annotations can protect events. Rules can protect events and incoming Url's.

  • rules allow you to change your action (override or redirect) and target on each rule. With annotations you can only use your configured default action and target.

  • when stored in a file or database, rules can be edited by admins at runtime.

Security Rules

Global Rules can be declared in your config/ColdBox.cfc in plain CFML or in any module's ModuleConfig.cfc or they can come from the following global sources:

  • A json file

  • An xml file

  • The database by adding the configuration settings for it

  • A model by executing a getSecurityRules() method from it

Rule Anatomy

A rule is a struct that can be composed of the following elements. All of them are optional except the secureList.

rules = [
    {
        "whitelist"     : "", // A list of white list events or Uri's
        "securelist"    : "", // A list of secured list events or Uri's
        "match"            : "event", // Match the event or a url
        "roles"            : "", // Attach a list of roles to the rule
        "permissions"    : "", // Attach a list of permissions to the rule
        "redirect"         : "", // If rule breaks, and you have a redirect it will redirect here
        "overrideEvent"    : "", // If rule breaks, and you have an event, it will override it
        "useSSL"        : false, // Force SSL,
        "action"        : "", // The action to use (redirect|override) when no redirect or overrideEvent is defined in the rule.
        "module"        : "" // metadata we can add so mark rules that come from modules
    };
]

Global Rules

Rules can be declared globally in your config/ColdBox.cfc or they can also be place in any custom module in your application:

config/Coldbox.cfc
// CB Security
cbSecurity : {
    // Global Relocation when an invalid access is detected, instead of each rule declaring one.
    "invalidAuthenticationEvent"     : "main.index",
    // Global override event when an invalid access is detected, instead of each rule declaring one.
    "invalidAuthorizationEvent"        : "main.index",
    // Default invalid action: override or redirect when an invalid access is detected, default is to redirect
    "defaultAuthorizationAction"    : "redirect",
    // The global security rules
    "rules"                         : [
        // should use direct action and do a global redirect
        {
            "whitelist": "",
            "securelist": "admin",
            "match": "event",
            "roles": "admin",
            "permissions": "",
            "action" : "redirect"
        },
        // no action, use global default action
        {
            "whitelist": "",
            "securelist": "noAction",
            "match": "url",
            "roles": "admin",
            "permissions": ""
        },
        // Using overrideEvent only, so use an explicit override
        {
            "securelist": "ruleActionOverride",
            "match": "url",
            "overrideEvent": "main.login"
        },
        // direct action, use global override
        {
            "whitelist": "",
            "securelist": "override",
            "match": "url",
            "roles": "",
            "permissions": "",
            "action" : "override"
        },
        // Using redirect only, so use an explicit redirect
        {
            "securelist": "ruleActionRedirect",
            "match": "url",
            "redirect": "main.login"
        }
    ]
    }
};

Annotation Security

The firewall will inspect handlers for the secured annotation. This annotation can be added to the entire handler or to an action or both. The default value of the secured annotation is a Boolean true. Which means, we need a user to be authenticated in order to access it.

handlers
// Secure this handler
component secured{

    function index(event,rc,prc){}
    function list(event,rc,prc){}

}

// Same as this
component secured=true{
}

// Not the same as this
component secured=false{
}
// Or this
component{

    function index(event,rc,prc) secured{

    }

    function list(event,rc,prc) secured="list"{

    }

}

Authorization Context

You can also give the annotation some value, which can be anything you like: A list of roles, a role, a list of permissions, metadata, etc. Whatever it is, this is the authorization context and the user validator must be able to not only authenticate but authorize the context or an invalid authorization will occur.

handler/users.cfc
// Secure this handler
component secured="admin,users"{

    function index(event,rc,prc) secured="list"{

    }

    function save(event,rc,prc) secured="write"{

    }

}

Cascading Security

By having the ability to annotate the handler and also the action you create a cascading security model where they need to be able to access the handler first and only then will the action be evaluated for access as well.

Security Validator

As we mentioned at the beginning of this overview, the security module will use a Validator object in order to determine if the user has authentication/authorization or not. This setting is the validator setting and will point to the WireBox ID that implements the following methods: ruleValidator() and annotationValidator().

models/MyValidator.cfc
/**
 * This function is called once an incoming event matches a security rule.
 * You will receive the security rule that matched and an instance of the ColdBox controller.
 *
 * You must return a struct with two keys:
 * - allow:boolean True, user can continue access, false, invalid access actions will ensue
 * - type:string(authentication|authorization) The type of block that ocurred.  Either an authentication or an authorization issue.
 *
 * @return { allow:boolean, type:string(authentication|authorization) }
 */
struct function ruleValidator( required rule, required controller );

/**
 * This function is called once access to a handler/action is detected.
 * You will receive the secured annotation value and an instance of the ColdBox Controller
 *
 * You must return a struct with two keys:
 * - allow:boolean True, user can continue access, false, invalid access actions will ensue
 * - type:string(authentication|authorization) The type of block that ocurred.  Either an authentication or an authorization issue.
 *
 * @return { allow:boolean, type:string(authentication|authorization) }
 */
struct function annotationValidator( required securedValue, required controller );

Each validator must return a struct with the following keys:

  • allow:boolean A Boolean indicator if authentication or authorization was violated

  • type:stringOf(authentication|authorization) A string that indicates the type of violation: authentication or authorization.

  • messages:string Info or debugging messages

CBAuthValidator

ColdBox security ships with the CBAuthValidator@cbsecurity which is the default validator in the configuration setting validator setting.

cbsecurity = {
    validator = "CBAuthValidator@cbsecurity"
}

When using the default CBAuthValidator@cbsecurity you also have to configure the cbauth module.

  cbAuth: {
    userServiceClass: "UserService"
  }

CFValidator

ColdBox security ships also with a CFML authentication and authorization validator called CFSecurity which has the following WireBox ID: CFValidator@cbsecurity and can be found at cbsecurity.models.CFSecurity

You basically use cfloginuser to log in a user and set their appropriate roles in the system. The module can then match to these roles via the security rules you have created.

cbsecurity/models/CFValidator.cfc
struct function ruleValidator( required rule, required controller ){
    return validateSecurity( arguments.rule.roles );
}

struct function annotationValidator( required securedValue, required controller ){
    return validateSecurity( arguments.securedValue );
}

private function validateSecurity( required roles ){
    var results = { "allow" : false, "type" : "authentication" };

    // Are we logged in?
    if( isUserLoggedIn() ){

        // Do we have any roles?
        if( listLen( arguments.roles ) ){
            results.allow     = isUserInAnyRole( arguments.roles );
            results.type     = "authorization";
        } else {
            // We are satisfied!
            results.allow.true;
        }
    }

    return results;
}

Custom Validators

The second method of authentication is based on your custom security logic. You will be able to register a validation object with the module. Once a rule is matched, the module will call your validation object, send in the rule/annotation value and ask if the user can access it or not. It will be up to your logic to determine if the rule is satisfied or not. Below is a sample permission based security validator:

models/MySecurity.cfc
component singleton{

    struct function ruleValidator( required rule, required controller ){
        return permissionValidator( rule.permissions, controller, rule );
    }

    struct function annotationValidator( required securedValue, required controller ){
        return permissionValidator( securedValue, controller );
    }

    private function permissionValidator( permissions, controller, rule ){
        var results = { "allow" : false, "type" : "authentication" };
        var user     = getCurrentUser();

        // First check if user has been authenticated.
        if( user.isLoaded() AND user.isLoggedIn() ){
            // Do we have the right permissions
            if( len( arguments.permissions ) ){
                results.allow     = user.checkPermission( arguments.permission );
                results.type     = "authorization";
            } else {
                results.allow = true;
            }
        }

        return results;
    }
}

Authentication vs Authorization

The security module can distinguish between authentication issues and authorization issues. Once these actions are identified, the security module can act upon the result of these actions. These actions are based on the following 4 settings, but they all come down to two outcomes:

  • a relocation to another event or URL

  • an event override

Setting

Default

Description

invalidAuthenticationEvent

---

The global invalid authentication event or URI or URL to go if an invalid authentication occurs

defaultAuthenticationAction

redirect

Default Authentication Action: override or redirect when a user has not logged in

invalidAuthorizationEvent

---

The global invalid authorization event or URI or URL to go if an invalid authorization occurs

defaultAuthorizationAction

redirect

Default Authorization Action: override or redirect when a user does not have enough permissions to access something

Interceptions

When invalid authentication or authorizations occur the interceptor will announce the following events:

  • cbSecurity_onInvalidAuthentication

  • cbSecurity_onInvalidAuthorization

You will receive the following data in the interceptData struct:

  • ip : The offending IP address

  • rule : The security rule intercepted or empty if annotations

  • settings : The firewall settings

  • validatorResults : The validator results

  • annotationType : The annotation type intercepted, handler or action or empty if rule driven

  • processActions : A Boolean indicator that defaults to true. If you change this to false, then the interceptor won't fire the invalid actions. Usually this means, you manually will do them.

You can use these security listeners to do auditing, logging, or even override the result of the operation.

There are many more interception points available to you, check them out in our Interceptions page.

CBSecurity Model

The CBSecurity model was introduced in version 2.3.0 and it provides you with a way to provide authorization checks and contexts anywhere you like: handlers, layouts, views, interceptors and even models.

Getting access to the model is easy via our cbSecure() mixin (handlers/layouts/views/interceptors) or injecting it via WireBox:

// Mixin approach
cbSecure()

// Injection
property name="cbSecurity" inject="@CBSecurity";

Once injected you can leverage it using our awesome methods listed below:

Blocking Methods

When certain permission context is met, if not throws NotAuthorized

  • secure( permissions, [message] )

  • secureAll( permissions, [message] )

  • secureNone( permissions, [message] )

  • secureWhen( context, [message] )

// Only allow access to user_admin
cbSecure().secure( "USER_ADMIN" );

// Only allow access if you have all of these permissions
cbSecure().secureAll( "EDITOR, POST_PUBLISH" )

// YOu must not have this permission, if you do, kick you out
cbSecure().secureNone( "FORGEBOX_USER" )

// Secure using security evaluations
// Kick out if you do not have the AUTHOR_ADMIN or you are not the same incoming author
cbSecurity.secureWhen( 
    cbSecurity.none( "AUTHOR_ADMIN" ) && 
    !cbSecurity.sameUser( oAuthor )  
)

// Secure using a closure 
cbSecurity.secureWhen( ( user ) => !user.isConfirmed() );

Action Context Methods

When certain permission context is met, execute the success function/closure, else if a fail closure is defined, execute that instead.

  • when( permissions, success, fail )

  • whenAll( permissions, success, fail )

  • whenNone( permissions, success, fail )

var oAuthor = authorService.getOrFail( rc.authorId );
prc.data = userService.getData();

// Run Security Contexts
cbSecure()
    // Only user admin can change to the incoming role
    .when( "USER_ADMIN", ( user ) => oAuthor.setRole( roleService.get( rc.roleID ) ) )
    // The system admin can set a super admin
    .when( "SYSTEM_ADMIN", ( user ) => oAuthor.setRole( roleService.getSystemAdmin() ) )
    // Filter the data to be shown to the user
    .when( "USER_READ_ONLY", ( user ) => prc.data.filter( ( i ) => !i.isClassified ) )

// Calling with a fail closure
cbSecurity.when(
    "USER_ADMIN",
    ( user ) => user.setRole( "admin" ), //success
    ( user ) => relocate( "Invaliduser" ) //fail
);

Verification Methods

Verify permissions or user equality

  • has( permissions ):boolean

  • all( permissions ):boolean

  • none( permissions ):boolean

  • sameUser( user ):boolean

function edit( event, rc, prc ){
    var oUser = userService.getOrFail( rc.id ?: "" );
    if( !sameUser( oUser ) ){
        relocate( "/users" );
    }
}

<cfif cbsecure().all( "USER_ADMIN,USER_EDITOR" )>
    This is only visible to user admins!
</cfif>

<cfif cbsecure().has( "SYSTEM_ADMIN" )>
    <a href="/user/impersonate/#prc.user.getId()#">Impersonate User</a>
</cfif>

<cfif cbsecure().sameUser( prc.user )>
    <i class="fa fa-star">This is You!</i>
</cfif>

Request Context Methods

  • secureView( permissions, successView, failView )

handlers/users.cfc
component{

    function index( event, rc, prc ){
     event.secureView( "USER_ADMIN", "users/admin/index", "users/index" ); 
    }

}

Security Visualizer

This module also ships with a security visualizer that will document all your security rules and your settings in a nice panel. In order to activate it you must add the enableSecurityVisualizer setting to your config and mark it as true. Once enabled you can navigate to: /cbsecurity and you will be presented with the visualizer.

Important The visualizer is disabled by default and if it detects an environment of production, it will disable itself.

JSON Web Tokens (JWT) REST Security

ColdBox Security offers a comprehensive feature set for RESTFul applications that require JSON web tokens. We offer both access and refresh token capabilities. Check out our JWT Section for an in-depth overview.

IntroductioncbAuth

What's New With 2.5.0

2020-APR-03

In this release we have updated our internal authentication library cbauth to version 5.x. Which brings the following changes to cbauth:

  • Added preLogin, postLogin, preLogout, postLogout interception points

  • authentication() now returns the user if valid

Just run update cbsecurity and you are done!

Logo
Logo

Token Storage

You can enable token storage in cbsecurity via the tokenStorage setting. By default it is enabled and leverages CacheBox's default cache using a key prefix of cbjwt_ + the token's unique identifier claim of jti.

We recommend that you create a separate provider for the cache.

Why use a storage?

The storage of keys are great in order to visualize in your application all the registered keys in the system. You can also invalidate keys, as by default if the token does not exist in the storage, it is considered invalid.

You can retrieve the token storage by injection or the helper method:

property name="tokenStorage" inject="DBTokenStorage@cbsecurity";
property name="tokenStorage" inject="CacheTokenStorage@cbsecurity";

jwtAuth().getTokenStorage()

Storage Drivers

We ship with two drivers:

  • cachebox : Leverages any cache registered in CacheBox

  • db : Leverages a database table to store the keys

CacheBox Driver Properties

  • cacheName : The cache to use

DB Driver Properties

  • table : The table to use for storage

  • schema : A schema to use if the database supports it, else empty

  • dns : The datasource to use, defaults to the one set in Application.cfc

  • autoCreate:true : Autocreate the table if not found

  • rotationDays:7 : How many days should the expiration be before removal

  • rotationFrequency:60 : How many minutes should pass before issuing a rotation check

The columns it will create are:

  • id - identifier

  • cacheKey - The unique cacke key, indexed

  • token - The encrypted token

  • expiration - The expiration

  • issued - The issue date

  • subject - The subject identifier

Custom Token Storage

If you would like to create your own token storage, just add your own WireBox ID to the driver, properties and implement the following interface: cbsecurity.interfaces.jwt.IJwtStorage

cbsecurity.interfaces.jwt.IJwtStorage.cfc
interface{

    /**
     * Configure the storage by passing in the properties
     * 
     * @return JWTStorage
     */
    any function configure( required properties );

    /**
     * Set a token in the storage
     * 
     * @key The cache key
     * @token The token to store
     * @expiration The token expiration
     * 
     * @return JWTStorage
     */
    any function set( required key, required token, required expiration );

    /**
     * Verify if the passed in token key exists
     * 
     * @key The cache key
     */
    boolean function exists( required key );

    /**
     * Retrieve the token via the cache key, if the key doesn't exist a TokenNotFoundException will be thrown
     * 
     * @key The cache key
     * @defaultValue If not found, return a default value
     *
     * @throws TokenNotFoundException
     */
    any function get( required key, defaultValue );

    /**
     * Invalidate/delete one or more keys from the storage
     *
     * @key A cache key or an array of keys to clear
     * 
     * @return JWTStorage
     */
    any function clear( required any key );

    /**
     * Clear all the keys in the storage
     *
     * @async Run in a separate thread
     * 
     * @return JWTStorage
     */
    any function clearAll( boolean async=false );

    /**
     * Retrieve all the jwt keys stored in the storage
     */
    array function keys();

    /**
     * The size of the storage
     */
    numeric function size();

}

Cross Site Request Forgery

This feature set is provided by the cbcsrf module.

Since version 2.4.x we have added the cbcsrf module as a dependency of cbSecurity. Below is how you can use it:

Settings

Below are the settings you can use for this module. Remember you must create the cbcsrf struct in your ColdBox.cfc under the moduleSettings structure:

Mixins

This module will add the following UDF mixins to handlers, interceptors, layouts and views:

  • csrfToken() : To generate a token, using the default or a custom key

  • csrfVerify() : Verify a valid token or not

  • csrf() : To generate a hidden field (csrf) with the token

  • csrfField() : Generate a random token in a hidden form element and javascript that will refresh the page automatically when the token expires

  • csrfRotate() : To wipe and rotate the tokens for the user

Here are the method signatures:

Mappings

The module also registers the following mapping in WireBox: @cbcsrf so you can call our service model directly.

Automatic Token Expiration

By default, the module is configured to rotate all user csrf tokens every 30 minutes. This means that every token that gets created has a maximum life-span of {rotationTimeout} minutes. If you do NOT want the tokens to EVER expire during the user's logged in session, then use the value of 0 zero.

It is recommended to rotate your keys often, in case your token get's compromised.

Token Rotation

We have provided several methods to rotate or clear out all of a user's tokens. If you are using cbAuth as your module of choice for authentication, then we will listen to logins and logouts and rotate the keys for you if you have enabled the enableAuthTokenRotator setting.

If you are NOT using cbAuth then we recommend you leverage the csrfRotate() mixin or the cbsrf.rotate() method on the @cbsrf model and do the manual rotation yourself.

Simple Example

Below is a simple example of manually verifying tokens in your handlers:

Automatic Token Verifier

We have included an interceptor that if loaded will verify all incoming requests to make sure the token has been passed or it will throw an exception.

The settings for this feature are:

You can also register an array of regular expressions that will be tested against the incoming event and if matched, it will allow the request through with no verification.

The verification process is as follows:

  • If we are doing an integration test, then skip verification

  • If the incoming HTTP Method is a get,options or head skip verification

  • If the incoming event matches any of the verifyExcludes setting, then skip verification

  • If the action is marked with a skipCsrf annotation, then skip verification

  • If no rc.csrf exists and no x-csrf-token header exists, throw a

    TokenNotFoundException exception

  • If the token is invalid then throw a TokenMismatchException exception

Please note that this verifier will check the following locations for the token:

  1. The request collection (rc) via the cbcsrf key

  2. The request HTTP header (x-csrf-token) key

skipCsrf Annotation

You can also annotate your event handler actions with a skipCsrf annotation and the verifier will also skip the verification process for those actions.

/cbcsrf/generate Endpoint

This module also allows you to turn on the generation HTTP endpoint via the enableEndpoint boolean setting. When turned on the module will register the following route: GET /cbcsrf/generate/:key?. You can use this endpoint to generate tokens for your users via AJAX or UI only applications. Please note that you can pass an optional /:key URL parameter that will generate the token for that specific key.

This endpoint should be secured, so we have annotated it with a secured annotation so if you are using cbSecurity or cbGuard this endpoint will only be available to logged in users.

moduleSettings = {
    cbcsrf : {
        // By default we load up an interceptor that verifies all non-GET incoming requests against the token validations
			enableAutoVerifier     : false,
			// A list of events to exclude from csrf verification, regex allowed: e.g. stripe\..*
			verifyExcludes         : [],
			// By default, all csrf tokens have a life-span of 30 minutes. After 30 minutes, they expire and we aut-generate new ones.
			// If you do not want expiring tokens, then set this value to 0
			rotationTimeout        : 30,
			// Enable the /cbcsrf/generate endpoint to generate cbcsrf tokens for secured users.
			enableEndpoint         : false,
			// The WireBox mapping to use for the CacheStorage
			cacheStorage           : "CacheStorage@cbstorages",
			// Enable/Disable the cbAuth login/logout listener in order to rotate keys
			enableAuthTokenRotator : false
    }
};
/**
 * Provides a random token and stores it in the coldbox cache storages. You can also provide a specific key to store.
 *
 * @key A random token is generated for the key provided.
 * @forceNew If set to true, a new token is generated every time the function is called. If false, in case a token exists for the key, the same key is returned.
 *
 * @return csrf token
 */
string function csrfToken( string key='', boolean forceNew=false )

/**
 * Validates the given token against the same stored in the session for a specific key.
 *
 * @token Token that to be validated against the token stored in the session.
 * @key The key against which the token be searched.
 *
 * @return Valid or Invalid Token
 */
boolean function csrfVerify( required string token='', string key='' )

/**
 * Generate a random token and build a hidden form element so you can submit it with your form
 *
 * @key A random token is generated for the key provided.
 * @forceNew If set to true, a new token is generated every time the function is called. If false, in case a token exists for the key, the same key is returned.
 *
 * @return HTML of the hidden field (csrf)
 */
string function csrf( string key='', boolean forceNew=false )

/**
 * Generate a random token in a hidden form element and javascript that will refresh the page automatically when the token expires
 * 
 * @key A random token is generated for the key provided. CFID is the default
 * @forceNew If set to true, a new token is generated every time the function is called. If false, in case a token exists for the key, the same key is returned.
 *
 * @return HTML of the hidden field (csrf)
 */
string function csrfField( string key='', boolean forceNew=false )

/**
 * Clears out all csrf token stored
 */
function csrfRotate()
component{

    property name="cbcsrf" inject="@cbcsrf";
    
}
moduleSettings = {
    cbcsrf : {
			// Enable/Disable the cbAuth login/logout listener in order to rotate keys
			enableAuthTokenRotator : true
    }
};
handlers/security.cfc
component{

    function doLogin( event, rc, prc ){
    
        if( valid login ){
            // login user
            csrfRotate();
        }
    }
    
    function logout( event, rc, prc  ){
        csrfRotate();
    }

}
registration.cfc
component extends="coldbox.system.EventHandler"{

     function signUp( event, rc, prc ){
        // Store this in a hidden field in the form
        prc.token = csrfGenerate();
        event.setView( "registration/signup" );
    }

     function signUpProcess( event, rc, prc ){
        // Verify CSFR token from form
        if( csrfVerify( rc.token ?: '' ) {
            // save form
        } else {
            // Something isn't right
            relocate( 'handler.signup' );
        }
    }
}
cbcsrf : {
    // Enable the verifier
    enableAutoVerifier : true,
    
    // A list of events to exclude from csrf verification, regex allowed: e.g. stripe\..*
    verifyExcludes : [
    
    ]
}
component{

    function doTestSave( event, rc, prc ) skipCsrf{


    }

}

What's New With 2.3.0

2020-MAR-30

This release focuses on bringing a strong focus on protecting every development layer of a ColdBox application. It introduces the cbSecurity model that can be used by any layer and provides you with a great functional API to secure your code.

Explicit Authorizations

There will be times where you will need authorization checks outside of the incoming request rules or the handler annotations. This can be from within interceptors, models, layouts or even views. For this, we have provided the cbSecurity model so you can do explicit authorization checks anywhere you like.

cbSecurity Model

You can inject our model or you can use our handy cbsecure() mixin (interceptors/handlers/layouts/views) and then call the appropriate security functions:

// Mixin: Handlers/Interceptors/Layouts/Views
cbsecure()

// Injection
property name="cbSecurity" inject="@cbSecurity"

All security methods will call the application's configured Authentication Service to retrieve the currently logged in user. If the user is not logged in an immediate NoUserLoggedIn exception will be thrown by all methods.

The secure() Methods

Now that you have access to the model, you can use the following method to verify explicit permissions and authorize access. This method will throw an exception if the user does not validate the incoming permissions context (NotAuthorized).

// Verify the currently logged in user has those permission, 
// else throw a NotAuthorized exception
cbSecurity.secure( permissions, [message] );
cbsecure().secure( permissions, [message] );
  • The permission can be an array, string or list of the permissions to validate.

  • The message is a custom error message to be used in the message string of the exception thrown.

You also have two more authorization methods that will verify certain permission conditions for you:

// Authorize that the user has ALL of the incoming permissions
cbSecurity.secureAll( permissions, [message] );
// Authorize that the user has NONE of the incoming permissions
cbSecurity.secureNone( permissions, [message] );

Conditional Authorizations Using when()

There are also cases where you want to execute a piece of code by determining if the user has access to do so. For example, only a USER_ADMIN can change people's roles or you want to filter some data for certain users. For this, we have created the when() method with the following signature:

when( permissions, success, fail )
  • The permissions is a permission array or list that will be Or'ed

  • The success is a closure/lambda or UDF that will execute if the permissions validate.

  • The fail is a closure/lambda or UDF that will execute if the permissions DID not validate, much like an else statement

Both closures/functions takes in a user which is the currently authenticated user, the called in permissions and can return anything.

// Lambda approach
( user, permissions ) => { 
    // your code here
};
// UDF/Closure
function( user, permissions ){ 
    // your code here
}

You can also chain the when() calls if needed, to create beautiful security contexts. So if we go back to our admin examples, we can do something like this:

var oAuthor = authorService.getOrFail( rc.authorId );
prc.data = userService.getData();

// Run Security Contexts
cbSecure()
    // Only user admin can change to the incoming role
    .when( "USER_ADMIN", ( user ) => oAuthor.setRole( roleService.get( rc.roleID ) ) )
    // The system admin can set a super admin
    .when( "SYSTEM_ADMIN", ( user ) => oAuthor.setRole( roleService.getSystemAdmin() ) )
    // Filter the data to be shown to the user
    .when( "USER_READ_ONLY", ( user ) => prc.data.filter( ( i ) => !i.isClassified ) )

// Calling with a fail closure
cbSecurity.when(
    "USER_ADMIN",
    ( user ) => user.setRole( "admin" ), //success
    ( user ) => relocate( "Invaliduser" ) //fail
);

We have also added the following whenX() methods to serve your needs when evaluating the permissions:

// When all permissions must exist in the user
whenAll( permissions, success, fail)
// When none of the permissions exist in the user
whenNone( permissions, success, fail )

Verification Methods

If you just want to validate if a user has certain permissions or maybe no permissions at all or if a passed user is the same as the logged in user, then you can use the following boolean methods that only do verification.

Please note that you could potentially do these type of methods by leveraging the currently logged in user and it's hasPermission() method. However, these methods provide abstraction and can easily be mocked!

// Checks the user has one or at least one permission if the 
// permission is a list or array
boolean cbSecurity.has( permission );
// The user must have ALL the permissions
boolean cbSecurity.all( permission );
// The user must NOT have any of the permissions
boolean cbSecurity.none( permission );
// Verify if the passed in user is the same as the logged in user
boolean cbSecurity.sameUser( user );

These are great to have a unified and abstracted way to verifying permissions or if the passed user is the same as the logged in user. Here are some examples:

View Layer

<cfif cbsecure().has( "USER_ADMIN" )>
    This is only visible to user admins!
</cfif>

<cfif cbsecure().has( "SYSTEM_ADMIN" )>
    <a href="/user/impersonate/#prc.user.getId()#">Impersonate User</a>
</cfif>

<cfif cbsecure().sameUser( prc.user )>
    <i class="fa fa-star">This is You!</i>
</cfif>

Other Layers:

if( cbSecurity.has( "PERM" ) ){
    auditUser();
}

if( cbSecurity.sameUser( prc.incomingUser ) ){
    // you can change your gravatar
}

Please note that we do user equality by calling the getId() method of the authenticated user and the incoming user. This is part of our IAuthUser interface requirements.

Authorization Contexts

There are also times where you need to validate custom conditions and block access to certain areas. This way, you can implement your own custom security logic and leverage cbSecurity for blockage. You will accomplish this via the secureWhen() method:

secureWhen( context, [errorMessage] )

The context can be a closure/lambda/udf or a boolean evaluation:

// Using as a closure/lambda
cbSecurity.secureWhen( ( user ) => !user.isConfirmed() )
cbSecurity.secureWhen( ( user ) => !oEntry.canPublish( user ) )

// Using a boolean evaluation
cbSecurity.secureWhen( cbSecurity.none( "AUTHOR_ADMIN" ) && !cbSecurity.sameUser( oAuthor )  )
cbSecurity.whenNone( "AUTHOR_ADMIN", ( user ) => relocate() );

The closure/udf will receive the currently authenticated user as the first argument.

( user ) => {}
function( user );

Securing Views

You can also use our handy event.secureView() method in the request context to pivot between views according to user permissions.

cbSecurity injects the secureView() method into the request context via the preProcess interception point.

event.secureView( permissions, successView, failView )

This will allow you to set the successView if the user has the permissions or the failView if they don't.

cbSecurity Method Summary

Blocking Methods

When certain permission context is met, if not throws NotAuthorized

  • secure( permissions, [message] )

  • secureAll( permissions, [message] )

  • secureNone( permissions, [message] )

  • secureWhen( context, [message] )

  • guard() alias to secure()

Action Context Methods

When certain permission context is met, execute the success function/closure, else if a fail closure is defined, execute that instead.

  • when( permissions, success, fail )

  • whenAll( permissions, success, fail )

  • whenNone( permissions, success, fail )

Verification Methods

Verify permissions or user equality

  • has( permissions ):boolean

  • all( permissions ):boolean

  • none( permissions ):boolean

  • sameUser( user ):boolean

Request Context Methods

  • secureView( permissions, successView, failView )

cbSecurity Model

This object is used to provide you with human, fluent and explicit security authorizations and contexts.

Explicit Authorizations

The cbSecurity model is a specialized service that will allow you to do explicit authorizations in any layer of your ColdBox application.

There will be times where you will need authorization checks outside of the incoming request rules or the handler annotations. This can be from within interceptors, models, layouts or even views. For this, we have provided the cbSecurity model so you can do explicit authorization checks anywhere you like.

cbSecurity Model

You can inject our model or you can use our handy cbsecure() mixin (handlers/layouts/views) and then call the appropriate security functions:

// Mixin: Handlers/Layouts/Views
cbsecure()

// Injection
property name="cbSecurity" inject="@cbSecurity"

All security methods will call the application's configured Authentication Service to retrieve the currently logged in user. If the user is not logged in an immediate NoUserLoggedIn exception will be thrown by all methods.

You can now discover our sections for securing using cbSecurity

  • Secure() blocking methods

  • Verification Methods

  • Authorization Contexts

  • Securing Views

cbSecurity Method Summary

Blocking Methods

When certain permission context is met, if not throws NotAuthorized

  • secure( permissions, [message] )

  • secureAll( permissions, [message] )

  • secureNone( permissions, [message] )

  • secureWhen( context, [message] )

  • guard() alias to secure()

Action Context Methods

When certain permission context is met, execute the success function/closure, else if a fail closure is defined, execute that instead.

  • when( permissions, success, fail )

  • whenAll( permissions, success, fail )

  • whenNone( permissions, success, fail )

Verification Methods

Verify permissions or user equality

  • has( permissions ):boolean

  • all( permissions ):boolean

  • none( permissions ):boolean

  • sameUser( user ):boolean

Request Context Methods

  • secureView( permissions, successView, failView )

JWT.IO - JSON Web Tokens Introduction
cflogin Code Examples and CFML Documentation
FORGEBOX: JWT CFML
Logo
Logo
Logo
cfloginuser Code Examples and CFML Documentation
Logo
Using ColdFusion security tags and functions
Logo

Installation

Leverage CommandBox to install into your ColdBox app:

# Latest version
install cbsecurity

# Bleeding Edge
install cbsecurity@be

System Requirements

  • Lucee 5.x+

  • ColdFusion 2016+

Module Settings

The module can be configured by adding a cbsecurity key in the moduleSettings structure within the config/Coldbox.cfc

config/Coldbox.cfc
// Module Settings
moduleSettings = {
    // CB Security
    cbSecurity : {
        // The global invalid authentication event or URI or URL to go if an invalid authentication occurs
        "invalidAuthenticationEvent"    : "",
        // Default Authentication Action: override or redirect when a user has not logged in
        "defaultAuthenticationAction"    : "redirect",
        // The global invalid authorization event or URI or URL to go if an invalid authorization occurs
        "invalidAuthorizationEvent"        : "",
        // Default Authorization Action: override or redirect when a user does not have enough permissions to access something
        "defaultAuthorizationAction"    : "redirect",
        // You can define your security rules here or externally via a source
        "rules"                            : [],
        // The validator is an object that will validate rules and annotations and provide feedback on either authentication or authorization issues.
        "validator"                        : "CBAuthValidator@cbsecurity",
        // The WireBox ID of the authentication service to use in cbSecurity which must adhere to the cbsecurity.interfaces.IAuthService interface.
        "authenticationService"          : "authenticationService@cbauth",
        // WireBox ID of the user service to use
        "userService"                     : "",
        // The name of the variable to use to store an authenticated user in prc scope if using a validator that supports it.
        "prcUserVariable"                 : "oCurrentUser",
        // If source is model, the wirebox Id to use for retrieving the rules
        "rulesModel"                    : "",
        // If source is model, then the name of the method to get the rules, we default to `getSecurityRules`
        "rulesModelMethod"                : "getSecurityRules",
        // If source is db then the datasource name to use
        "rulesDSN"                        : "",
        // If source is db then the table to get the rules from
        "rulesTable"                    : "",
        // If source is db then the ordering of the select
        "rulesOrderBy"                    : "",
        // If source is db then you can have your custom select SQL
        "rulesSql"                         : "",
        // Use regular expression matching on the rule match types
        "useRegex"                         : true,
        // Force SSL for all relocations
        "useSSL"                        : false,
        // Auto load the global security firewall
        "autoLoadFirewall"                : true,
        // Activate handler/action based annotation security
        "handlerAnnotationSecurity"        : true,
        // Activate security rule visualizer, defaults to false by default
        "enableSecurityVisualizer"        : false,
        // JWT Settings
 			  "jwt"                         : {
     				// The issuer authority for the tokens, placed in the `iss` claim
     				"issuer"                     : "",
     				// The jwt secret encoding key to use. This key is only effective within the `config/Coldbox.cfc`. Specifying within a module does nothing.
     				"secretKey"                  : getSystemSetting( "JWT_SECRET", "" ),
     				// by default it uses the authorization bearer header, but you can also pass a custom one as well or as an rc variable.
     				"customAuthHeader"           : "x-auth-token",
     				// The expiration in minutes for the jwt tokens
     				"expiration"                 : 60,
     				// If true, enables refresh tokens, token creation methods will return a struct instead
     				// of just the access token. e.g. { access_token: "", refresh_token : "" }
     				"enableRefreshTokens"        : false,
     				// The default expiration for refresh tokens, defaults to 30 days
     				"refreshExpiration"          : 10080,
     				// The Custom header to inspect for refresh tokens
     				"customRefreshHeader"        : "x-refresh-token",
     				// If enabled, the JWT validator will inspect the request for refresh tokens and expired access tokens
     				// It will then automatically refresh them for you and return them back as
     				// response headers in the same request according to the customRefreshHeader and customAuthHeader
     				"enableAutoRefreshValidator" : false,
     				// Enable the POST > /cbsecurity/refreshtoken API endpoint
     				"enableRefreshEndpoint"      : true,
     				// encryption algorithm to use, valid algorithms are: HS256, HS384, and HS512
     				"algorithm"                  : "HS512",
     				// Which claims neds to be present on the jwt token or `TokenInvalidException` upon verification and decoding
     				"requiredClaims"             : [],
     				// The token storage settings
     				"tokenStorage"               : {
         					// enable or not, default is true
         					"enabled"    : true,
         					// A cache key prefix to use when storing the tokens
         					"keyPrefix"  : "cbjwt_",
         					// The driver to use: db, cachebox or a WireBox ID
         					"driver"     : "cachebox",
         					// Driver specific properties
         					"properties" : { "cacheName" : "default" }
     				}
 			}
    }
};

If you are using cbauth as your authenticationService (the default), you also need to configure cbauth.