Skip to content

Latest commit

 

History

History
408 lines (327 loc) · 11.2 KB

File metadata and controls

408 lines (327 loc) · 11.2 KB
name coldbox-security-implementation
description Use this skill when setting up the full CBSecurity framework in ColdBox, configuring the security firewall, creating authentication services, implementing security event handlers, configuring security rules and validators, or building a complete security layer for a ColdBox application.

CBSecurity Implementation in ColdBox

Overview

CBSecurity is ColdBox's enterprise security framework providing authentication, authorization, firewall rules, security handlers, and a security event model that protects all routes/events.

Language Mode Reference

Examples use BoxLang (.bx) syntax by default. Adapt for your target language:

Concept BoxLang (.bx) CFML (.cfc)
Class declaration class [extends="..."] { component [extends="..."] {
DI annotation @inject above property name="svc"; property name="svc" inject="svc";
View templates .bxm suffix .cfm / .cfml suffix
Tag prefix <bx:if>, <bx:output>, <bx:set> <cfif>, <cfoutput>, <cfset>

CFML Compat Mode: With BoxLang + CFML Compat module, .bx and .cfc files coexist freely. BoxLang-native classes use class {} (.bx files); CFML-compat classes use component {} (.cfc files).

Installation

box install cbsecurity
box install cbauth
box install bcrypt

Full Configuration

// config/ColdBox.cfc
moduleSettings = {
    cbsecurity: {
        // Authentication service
        authenticationService: "SecurityService@models",

        // Firewall
        firewall: {
            enabled: true,
            defaultAction: "redirect",
            defaultRedirect: "/login",
            // For APIs: use "block" with 401
            statusCode: 401,
            // Override rejection targets
            invalidAuthenticationEvent: "auth.login",
            invalidAuthorizationEvent:   "main.accessDenied"
        },

        // Security rules (processed top-to-bottom, first match wins)
        rules: [
            {
                whitelist: "auth\\..*,main\\.index,main\\.about",
                match: "event"
            },
            {
                secureList: "^admin\\.",
                match: "event",
                roles: "admin,superadmin",
                action: "redirect",
                redirect: "/login"
            },
            {
                secureList: ".*",
                match: "event",
                action: "redirect",
                redirect: "/login"
            }
        ]
    }
}

Authentication Service

/**
 * models/SecurityService.cfc
 */
class singleton {

    property name="userService"     inject="UserService"
    property name="bcrypt"          inject="@BCrypt"
    property name="sessionStorage"  inject="sessionStorage@cbstorages"

    function authenticate( required username, required password ) {
        try {
            var user = userService.findByUsername( arguments.username )

            if ( !bcrypt.checkPassword( arguments.password, user.password ) ) {
                throw( type: "InvalidCredentials" )
            }

            sessionStorage.set( "currentUser", user )
            return true

        } catch ( any e ) {
            return false
        }
    }

    function getUser() {
        return sessionStorage.get( "currentUser", {} )
    }

    function isLoggedIn() {
        return sessionStorage.exists( "currentUser" )
    }

    function logout() {
        sessionStorage.delete( "currentUser" )
    }

    function hasRole( required role ) {
        var user = getUser()
        return structIsEmpty( user ) ? false : listContains( user.roles, arguments.role )
    }

    function can( required permission ) {
        var user = getUser()
        return structIsEmpty( user ) ? false : listContains( user.permissions, arguments.permission )
    }
}

CFML (.cfc):

/**
 * models/SecurityService.cfc
 */
component {

    property name="userService"     inject="UserService"
    property name="bcrypt"          inject="@BCrypt"
    property name="sessionStorage"  inject="sessionStorage@cbstorages"

    function authenticate( required username, required password ) {
        try {
            var user = userService.findByUsername( arguments.username )

            if ( !bcrypt.checkPassword( arguments.password, user.password ) ) {
                throw( type: "InvalidCredentials" )
            }

            sessionStorage.set( "currentUser", user )
            return true

        } catch ( any e ) {
            return false
        }
    }

    function getUser() {
        return sessionStorage.get( "currentUser", {} )
    }

    function isLoggedIn() {
        return sessionStorage.exists( "currentUser" )
    }

    function logout() {
        sessionStorage.delete( "currentUser" )
    }

    function hasRole( required role ) {
        var user = getUser()
        return structIsEmpty( user ) ? false : listContains( user.roles, arguments.role )
    }

    function can( required permission ) {
        var user = getUser()
        return structIsEmpty( user ) ? false : listContains( user.permissions, arguments.permission )
    }
}

Security Interceptor

/**
 * interceptors/SecurityInterceptor.cfc
 * Runs on every request to check auth status
 */
class extends="coldbox.system.Interceptor" {

    property name="cbsecurity" inject="@cbsecurity"

    function preProcess( event, interceptData ) {
        // Skip public routes
        if ( isPublicRoute( event.getCurrentEvent() ) ) {
            return
        }

        // Check authentication
        if ( !cbsecurity.isLoggedIn() ) {
            flash.put( "redirectTo", cgi.script_name & "?" & cgi.query_string )
            relocate( "auth.login" )
        }
    }

    private function isPublicRoute( required eventName ) {
        var publicRoutes = [
            "auth.login", "auth.doLogin",
            "auth.register", "auth.doRegister",
            "main.index"
        ]
        return publicRoutes.contains( arguments.eventName )
    }
}

CFML (.cfc):

/**
 * interceptors/SecurityInterceptor.cfc
 * Runs on every request to check auth status
 */
component extends="coldbox.system.Interceptor" {

    property name="cbsecurity" inject="@cbsecurity"

    function preProcess( event, interceptData ) {
        // Skip public routes
        if ( isPublicRoute( event.getCurrentEvent() ) ) {
            return
        }

        // Check authentication
        if ( !cbsecurity.isLoggedIn() ) {
            flash.put( "redirectTo", cgi.script_name & "?" & cgi.query_string )
            relocate( "auth.login" )
        }
    }

    private function isPublicRoute( required eventName ) {
        var publicRoutes = [
            "auth.login", "auth.doLogin",
            "auth.register", "auth.doRegister",
            "main.index"
        ]
        return publicRoutes.contains( arguments.eventName )
    }
}

LoginHandler

/**
 * handlers/Auth.cfc
 */
class extends="coldbox.system.EventHandler" {

    property name="security" inject="SecurityService@models"

    function login( event, rc, prc ) {
        if ( security.isLoggedIn() ) {
            relocate( uri = "main.dashboard" )
        }
        event.setView( "auth/login" )
    }

    function doLogin( event, rc, prc ) {
        event.paramValue( "email",    "" )
        event.paramValue( "password", "" )

        if ( security.authenticate( rc.email, rc.password ) ) {
            // Redirect to originally requested URL if available
            var destination = flash.get( "redirectTo", "main.dashboard" )
            relocate( uri = destination )
        } else {
            flash.put( "error", "Invalid credentials" )
            relocate( "auth.login" )
        }
    }

    function doLogout( event, rc, prc ) {
        security.logout()
        relocate( "auth.login" )
    }
}

CFML (.cfc):

/**
 * handlers/Auth.cfc
 */
component extends="coldbox.system.EventHandler" {

    property name="security" inject="SecurityService@models"

    function login( event, rc, prc ) {
        if ( security.isLoggedIn() ) {
            relocate( uri = "main.dashboard" )
        }
        event.setView( "auth/login" )
    }

    function doLogin( event, rc, prc ) {
        event.paramValue( "email",    "" )
        event.paramValue( "password", "" )

        if ( security.authenticate( rc.email, rc.password ) ) {
            // Redirect to originally requested URL if available
            var destination = flash.get( "redirectTo", "main.dashboard" )
            relocate( uri = destination )
        } else {
            flash.put( "error", "Invalid credentials" )
            relocate( "auth.login" )
        }
    }

    function doLogout( event, rc, prc ) {
        security.logout()
        relocate( "auth.login" )
    }
}

Security Events (Interceptors)

/**
 * interceptors/SecurityEvents.cfc
 * Log security events for audit trail
 */
class extends="coldbox.system.Interceptor" {

    property name="logger" inject="logbox:logger:{this}"

    function CBSecurity_onInvalidAuthentication( event, interceptData ) {
        logger.warn( "Authentication failure: event=#interceptData.event#, ip=#cgi.remote_addr#" )
    }

    function CBSecurity_onInvalidAuthorization( event, interceptData ) {
        logger.warn( "Authorization failure: event=#interceptData.event#, user=#interceptData.userId#" )
    }

    function CBSecurity_onAuthentication( event, interceptData ) {
        logger.info( "User logged in: #interceptData.userId#" )
    }

    function CBSecurity_onLogout( event, interceptData ) {
        logger.info( "User logged out: #interceptData.userId#" )
    }
}

CFML (.cfc):

/**
 * interceptors/SecurityEvents.cfc
 * Log security events for audit trail
 */
component extends="coldbox.system.Interceptor" {

    property name="logger" inject="logbox:logger:{this}"

    function CBSecurity_onInvalidAuthentication( event, interceptData ) {
        logger.warn( "Authentication failure: event=#interceptData.event#, ip=#cgi.remote_addr#" )
    }

    function CBSecurity_onInvalidAuthorization( event, interceptData ) {
        logger.warn( "Authorization failure: event=#interceptData.event#, user=#interceptData.userId#" )
    }

    function CBSecurity_onAuthentication( event, interceptData ) {
        logger.info( "User logged in: #interceptData.userId#" )
    }

    function CBSecurity_onLogout( event, interceptData ) {
        logger.info( "User logged out: #interceptData.userId#" )
    }
}

Access Denied Handler

/**
 * handlers/Main.cfc
 */
function accessDenied( event, rc, prc ) {
    event.setHTTPHeader( statusCode: 403 )
    event.setView( "errors/403" )
}

Security Checklist

  • Install cbsecurity, cbauth, bcrypt
  • Configure authenticationService in moduleSettings
  • Define firewall rules (whitelist public routes, secure the rest)
  • Use BCrypt for password hashing — never store plain text passwords
  • Implement role/permission checking in handlers with @secured
  • Log security events with LogBox
  • Use HTTPS in production
  • Set JWT_SECRET and other secrets via environment variables
  • Validate all user input at system boundaries