For the complete documentation index, see llms.txt. This page is also available as Markdown.

Route Middleware

Attach middleware to a route: closures, WireBox IDs, or plain objects that run before or after the matched handler.

.middleware() attaches a target to a route that runs at request time, scoped to just that route. It's not a new subsystem - it reuses the same interception dispatch ColdBox interceptors already use, just scoped to one route instead of the whole app.

route( "/admin/:action" )
    .middleware( ( event, rc, prc ) => {
        if ( !auth.isLoggedIn() ) {
            event.relocate( "login" );
            return true; // stop the remaining middleware for this route
        }
    } )
    .toHandler( "admin" );

A Target Can Be

  • A closure/lambda - ( event, rc, prc ) => { ... }, as above

  • A WireBox ID - resolved via getInstance() on every request, so it respects whatever scope (singleton, prototype, etc) the mapping was registered with

  • Any object - WireBox-managed or not, as long as it has a method named after the point it runs at (preProcess() by default). No base class or interface required

  • A registered name - a name given to any of the above with registerMiddleware(), or a middlewareGroup() name

// A concrete class, resolved by WireBox ID
class singleton {
    property name="auth" inject="AuthService";

    function preProcess( event, rc, prc ){
        if ( !auth.isLoggedIn() ) {
            event.relocate( "login" );
            return true;
        }
    }
}
// registered in WireBox as "RequireLogin"
route( "/admin/:action" ).middleware( "RequireLogin" ).toHandler( "admin" );
// A concrete class, resolved by WireBox ID
component singleton {
    property name="auth" inject="AuthService";

    function preProcess( event, rc, prc ){
        if ( !auth.isLoggedIn() ) {
            event.relocate( "login" );
            return true;
        }
    }
}
// registered in WireBox as "RequireLogin"
route( "/admin/:action" ).middleware( "RequireLogin" ).toHandler( "admin" );

Where It Runs

A target attaches to a point - preProcess (default) or postProcess - the second argument to .middleware():

Route middleware runs after the global preProcess announce and before the global postProcess announce - global interceptors stay the outermost layer, route-specific work happens closest to the handler.

Route middleware also runs in integration tests: BaseTestCase.execute() and the get(), post() and other HTTP helpers run it in the same order (since ColdBox 8.3.0). See The execute() Method.

Multiple Targets

Pass an array to attach several targets to the same point in one call. They run in order:

Short-Circuiting

Returning true from a target stops the remaining middleware for that route at that point. It does not, by itself, skip the handler or the render - call event.relocate(), event.renderData().noExecution(), or similar, exactly as you would from any other preProcess/postProcess interceptor:

Middleware is a flat before/after dispatch, not a wrapping pipeline - a single target can't run code both before and after the handler in one call. If you need genuine wrapping (timing a whole request, catching everything downstream), reach for aroundHandler on the handler itself instead.

Sharing Middleware Across Routes

Attaching middleware to a group applies it to every route declared inside, ahead of each route's own middleware:

Nested groups compose outer-first:

Registering Named Middleware

registerMiddleware( name, target ) gives a single closure, lambda, object, or WireBox ID a name, so you can reference it from many routes without repeating it. The target is any of the targets above, and the optional third argument is the point (preProcess by default, or postProcess).

A registered name works anywhere a middleware group name does: in .middleware(), in a group's middleware option, and in withoutMiddleware( name ). Under the hood it is stored as a one-member group in the same namespace, and registerMiddleware() returns the router so calls can be chained.

Registering Several at Once

Pass a struct of name : target pairs. Every entry uses the same point (preProcess unless you pass one):

CFML does not allow mixing positional and named arguments. To pass force with the struct form, name every argument: registerMiddleware( name = { auth : "Authenticated@cbsecurity" }, force = true ).

Duplicate Names

Registering a name that is already taken, either by another registerMiddleware() call or by a middlewareGroup(), throws a Router.DuplicateMiddleware exception. Pass force = true to replace the existing definition. A bulk call is all or nothing: if any name is a duplicate, nothing from that call is registered. A missing or empty name or target throws Router.InvalidMiddleware.

Middleware From Modules

The registry belongs to the router that registered it, and modules do not share one. A module that ships middleware should register it as a WireBox mapping and let apps reference the WireBox ID, as Authenticated@cbsecurity does. An application can then give that ID a short local name:

registerMiddleware() requires ColdBox 8.3+.

Securing Routes With cbsecurity

The cbsecurity module ships ready-made middleware, so you do not write login and permission checks yourself. Permissions and roles are declared in the route's meta():

cbsecurity Middleware Reference

Every cbsecurity middleware is a WireBox ID you pass to middleware(). Parameters go in the route's meta(), because modules load after the router. Each name links to its cbsecurity guide.

WireBox ID

What it does

Route meta() keys

The user must be logged in.

none

Logged in and has the permissions or roles.

permissions, roles, mode

Like Authorized, authenticating with a JWT.

permissions, mode

Like Authorized, authenticating with HTTP Basic.

permissions, roles, mode

Rate limits requests, answering 429 over the limit.

throttle

Requires an API key from the x-api-key header or apiKey request key.

apiKeys, apiKeyHeader, apiKeyParam

Only listed IPs and CIDR ranges get in.

allowedIps

Blocks listed IPs and CIDR ranges.

denyIps

Redirects to HTTPS, or denies non GET requests.

redirectToHttps

Verifies a CSRF token on unsafe requests.

csrfKey

Catches spam bots with a hidden form field.

honeypotField, honeypotSilent

Only lets valid, unexpired signed URLs through.

none

Stack them. They run in the order you list them, so put cheap checks first:

The authentication middleware send denied requests through the cbsecurity firewall's invalid action (redirect, override or block). The others answer directly with a JSON error and the right status code.

Throttle@cbsecurity and Signed@cbsecurity also come with models and helpers you can use outside routes: RateLimiter@cbsecurity, UrlSigner@cbsecurity and the signedRoute(), signedUrl() and hasValidSignature() helpers.

Defaults for these middleware (trusted proxies, API key header, throttle limiters, the signing secret) live in the cbsecurity route middleware settings. The full guide is the cbsecurity Route Middleware page.

Testing Routes With Middleware

The integration test helpers (execute(), get(), post() and friends) run route middleware in the same order as a real request, so you can assert on a blocked or redirected route.

Running route middleware inside execute() requires ColdBox 8.3+. On earlier versions the middleware is skipped in integration tests.

Last updated

Was this helpful?