Security & Permissions
Session auth, CSRF, JWT, security headers, and the resource:action permission model.
On this page
Security & Permissions
Login flow
sequenceDiagram
participant Form as Login Form
participant Auth as Auth.bx doLogin()
participant Sec as SecurityService
participant Store as cbauth / Session Cache
Form->>+Auth: POST /login (email + password)
Auth->>Auth: CSRF check + cbvalidation
Auth->>+Sec: authenticate( email, password )
Sec->>Sec: bcrypt verify
Sec->>+Store: cbauth.login() — write session
Store-->>-Sec: ok
Sec-->>-Auth: authenticated user
Auth-->>-Form: redirect → /dashboard
Authentication layouts
The authentication flow can use either shipped layout through the cbLoginLayout setting:
| Value | Layout | Best for |
|---|---|---|
AuthSplit | Branded feature panel on the left with the form on the right; it becomes compact on mobile. | Applications that want a branded, two-panel sign-in experience. This is the default. |
AuthCenter | Centered authentication card with the logo, form, and footer. | Applications that prefer a focused, compact sign-in experience. |
Choose Auth Center or Auth Split on the /settings page. The selected layout applies to login, registration, invitation activation, and password-recovery pages. See App Settings for the layout files and custom-layout instructions.
Security layers
| Layer | Implementation |
|---|---|
| Session auth | cbauth with CacheStorage@cbStorages — server-side session cache |
| Password hashing | bcrypt via bx-password-encrypt |
| CSRF protection | cbsecurity rotating token (30 min), verified manually per action — see Handlers & Routing |
| Handler security | @secured annotation → firewall redirects unauthenticated visitors to login, authorized-but-unpermitted users to dashboard.notAuthorized |
| JWT support | Configured for API access (AES-256, HS512, 60 min, cache token storage) |
| Security headers | XSS protection, frameOptions: SAMEORIGIN, referrerPolicy: same-origin |
| API tokens | SHA/BCrypt-hashed per-user tokens with expiration and a daily purge scheduler |
cbsecurity configuration
app/config/modules/cbsecurity.bx is the single source of truth for the firewall:
{
authentication : {
provider : "authenticationService@cbauth",
prcUserVariable : "authUser"
},
firewall : {
autoLoadFirewall : true,
validator : "CBAuthValidator@cbsecurity",
handlerAnnotationSecurity : true,
invalidAuthenticationEvent : "login",
invalidAuthorizationEvent : "dashboard.notAuthorized",
rules : [] // authorization is annotation-based, not rule-based
}
}
prcUserVariable: "authUser"— the authenticated user is always available asprc.authUserin every handler, view, and layout.handlerAnnotationSecurity: true— this is what makes@securedannotations on a handler class or action actually enforce anything.rules: []— this app does all of its authorization via handler annotations, not cbsecurity's alternative URL-pattern rule list.
Permission model
Every permission is a slug in the form resource:action, seeded by resources/database/seeds/AdminData.bx:
| Resource | Actions |
|---|---|
users | read, write, delete, admin |
roles | read, write, delete, admin |
permissions | read, write, delete, admin |
settings | read, write, delete, admin |
auditlog | read, export, admin |
admin means "full administration of that resource" and is always OR'd alongside the specific action a route needs, so a user holding roles:admin passes any roles:* check without also needing roles:read/roles:write/roles:delete individually. The seeder assigns the built-in permissions to a single Administrator role, granted to the seeded admin@cbgenesis.com user.
Enforce it on the handler — this is the real security boundary, resolved by cbsecurity's CBAuthValidator against the authenticated user's permissions:
@secured( "roles:admin,roles:read" ) // class-level: applies to index and any action without its own annotation
class extends="BaseSecureHandler" {
@secured( "roles:admin,roles:write" )
function create( event, rc, prc ) { ... }
@secured( "roles:admin,roles:delete" )
function delete( event, rc, prc ) { ... }
}
A comma-separated list is an OR check — any one of the listed permissions is enough.
Mirror it in the view — UX only, never the security boundary on its own. User.bx exposes hasPermission() on prc.authUser, available in any view or layout rendered through a secured handler:
<bx:if prc.authUser.hasPermission( "roles:write,roles:admin" )>
<button type="button" class="btn btn-primary" @click="openCreate()">New Role</button>
</bx:if>
hasPermission() accepts a string, comma-list, or array and does an OR check; hasAllPermissions() does the AND equivalent. Both are cached per-request via getAllPermissions(), which unions a user's à-la-carte permissions with every permission granted through their roles. Every existing admin view (sidebar nav, Users/Roles/Permissions/Settings) already follows this pattern — treat it as the template for new secured modules.
A user who fails an @secured check is redirected:
- Unauthenticated →
login - Authenticated, missing permission →
dashboard.notAuthorized
Related security services
| Model | Purpose |
|---|---|
SecurityService | Wraps cbauth's authentication service; login()/authenticate(), remember-me cookie management with token rotation, logout(), password-reset token issue/verify (cache-backed, not DB) |
APIToken / APITokenService | SHA/BCrypt-hashed personal access tokens — createToken() returns the raw token exactly once, revokeToken()/revokeAllForUser(), purgeExpiredTokens() on a schedule |
RememberToken / RememberTokenService | Persistent "remember me" browser tokens, rotated on every use |
UserActionToken / UserActionTokenService | Purpose-bound, single-use tokens (PURPOSE_REGISTRATION, PURPOSE_INVITATION) — issue(), resolve(), consume() |
Passkey / PasskeyService | WebAuthn credential storage via cbsecurity-passkeys' ICredentialRepository contract |