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:

ValueLayoutBest for
AuthSplitBranded 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.
AuthCenterCentered 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

LayerImplementation
Session authcbauth with CacheStorage@cbStorages — server-side session cache
Password hashingbcrypt via bx-password-encrypt
CSRF protectioncbsecurity 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 supportConfigured for API access (AES-256, HS512, 60 min, cache token storage)
Security headersXSS protection, frameOptions: SAMEORIGIN, referrerPolicy: same-origin
API tokensSHA/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 as prc.authUser in every handler, view, and layout.
  • handlerAnnotationSecurity: true — this is what makes @secured annotations 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:

ResourceActions
usersread, write, delete, admin
rolesread, write, delete, admin
permissionsread, write, delete, admin
settingsread, write, delete, admin
auditlogread, export, admin
`admin` is a superset

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:

  • Unauthenticatedlogin
  • Authenticated, missing permissiondashboard.notAuthorized
ModelPurpose
SecurityServiceWraps cbauth's authentication service; login()/authenticate(), remember-me cookie management with token rotation, logout(), password-reset token issue/verify (cache-backed, not DB)
APIToken / APITokenServiceSHA/BCrypt-hashed personal access tokens — createToken() returns the raw token exactly once, revokeToken()/revokeAllForUser(), purgeExpiredTokens() on a schedule
RememberToken / RememberTokenServicePersistent "remember me" browser tokens, rotated on every use
UserActionToken / UserActionTokenServicePurpose-bound, single-use tokens (PURPOSE_REGISTRATION, PURPOSE_INVITATION) — issue(), resolve(), consume()
Passkey / PasskeyServiceWebAuthn credential storage via cbsecurity-passkeys' ICredentialRepository contract
Edit this page Download Markdown Last updated Sep 1, 2026, 7:55:53 PM