Handlers & Routing
Every handler, its actions, and how Router.bx wires URLs to them.
On this page
Handlers & Routing
Handler map
| Handler | Base | Purpose |
|---|---|---|
Auth.bx | EventHandler | Login, registration, invitations, password reset - all public |
BaseSecureHandler.bx | RestHandler | Base class for every admin handler |
Dashboard.bx | BaseSecureHandler | The authenticated landing page |
Main.bx | EventHandler | Implicit-event handler - see Architecture |
Permissions.bx | BaseSecureHandler | Permission slug CRUD |
Profile.bx | BaseSecureHandler | Self-service profile, password, API tokens, passkeys |
Roles.bx | BaseSecureHandler | Role CRUD + user assignment |
Settings.bx | BaseSecureHandler | App settings registry |
Users.bx | BaseSecureHandler | User administration |
BaseSecureHandler
Every protected handler extends BaseSecureHandler, which forces the Admin layout in preHandler, redirects to profile/passkey-required when cbRequirePasskey is on and the user has none, and provides shared helpers (getApiResults(), ensureSortDirection(), getPagination()):
component extends="coldbox.system.RestHandler" {
function preHandler( event, rc, prc ){
event.setLayout( "Admin" );
// ...passkey enforcement...
}
}
Building a new secured handler starts the same way every time:
component extends="BaseSecureHandler" secured {
function index( event, rc, prc ){
prc.pageTitle = "My Page";
event.setView( "myhandler/index" );
}
}
Auth
No @secured annotation - these actions must stay reachable by guests:
login/doLogin(GET/POST) - CSRF-verified, callssecurityService.login(), supportsrememberMeregister/doRegister- gated by thecbAllowRegistrationsettingcheckEmailAvailability- JSON endpoint for live email-availability checksverifyRegistration- consumes aPURPOSE_REGISTRATIONaction tokenactivateInvitation/doActivateInvitation- sets a password for an invited, admin-created userforgotPassword/doForgotPassword- gated bycbAllowForgotPasswordresetPassword/doResetPassword- validates the reset token, sets a new passwordlogout- callssecurityService.logout()
preHandler redirects an already-authenticated visitor straight to the dashboard, and sets the layout from prc.settings.cbLoginLayout (AuthSplit by default - see guides/security.md).
Dashboard
@secured (any authenticated user, no specific permission required):
index- the dashboard homenotAuthorized- the target ofinvalidAuthorizationEvent, shown when an authenticated user is missing a required permission
Permissions
@secured("permissions:admin,permissions:read") at the class level:
indexcreate-@secured("permissions:admin,permissions:write")update/delete-@remote, same write/delete permissions
Profile
@secured self-service actions for the current user, all @remote AJAX endpoints except index:
index,passkeyRequiredsave,doPasswordChangelistTokens/createToken/updateToken/deleteToken- API tokenslistPasskeys/updatePasskey/deletePasskey
A static csrfVerify map on the class lists which of these actions require CSRF verification.
Roles
@secured("roles:admin,roles:read") at the class level; every action but index is @remote:
indexcreate/update/delete-@secured("roles:admin,roles:write"/"...:delete")users/availableUsers- list users on/available for a roleaddUser/removeUser-@secured("roles:admin")
Settings
@secured("settings:admin,settings:read") at the class level:
indexregistry/registrySearch- paginated settings registrycreateRegistry/updateRegistry/toggleRegistryStatus/deleteRegistry-settings:admin,settings:writesave- bulk save of core settings- Admin utilities (all
settings:admin):clearTemplateCache,clearSessionsCache,revokeRememberTokens,flushSettingsCache
Users
@secured("users:admin,users:read") at the class level:
index,searchcreate/update/delete/resendInvitation-users:admin,users:write/...:deleteshow-users:read- Admin-only (
users:admin):updateProfile,setStatus,resetPassword,verify,revokeRememberTokens,addRole/removeRole,addPermission/removePermission,savePreferences,revokeToken/revokeAllTokens
ensureNotSelf() guards several of these to block an admin from demoting or removing their own roles.
app/config/modules/cbsecurity.bx sets csrf.enableAutoVerifier: false - every state-changing action verifies CSRF itself (via a preHandler check or a static.csrfVerify map), rather than relying on a global interceptor. Follow the existing pattern in the handler you're extending.
Route map (app/config/Router.bx)
All routes are declared in one configure() function:
route( "/healthcheck" ).to( () => "Ok!" );
get( "dashboard" ).to( "Dashboard.index" );
resources( "permissions", parameterName = "permissionId" );
route( "roles/:roleId/available-users" ).to( "Roles.availableUsers" );
route( "roles/:roleId/users" ).toAction( { POST: "addUser" } );
route( "roles/:roleId/users/:userId" ).toAction( { DELETE: "removeUser" } );
resources( "roles", parameterName = "roleId" );
resources( "users", parameterName = "userId" );
route( "profile" ).toAction( { GET: "index", POST: "save" } );
// @app_routes@ ← insertion point for module/scaffold-generated routes
route( ":handler/:action?" ).end(); // conventions-based catch-all
See Reference: Route Map for the full table of every method, URL, target action, and required permission.