Skip to content
Warlock.js v4.2.11

Your first check

Two checks, one for each stage of the model.

Stack gate after authMiddleware — the auth middleware hydrates request.user, then the access middleware checks the grant:

src/app/orders/routes.ts
import { authMiddleware } from "@warlock.js/auth";
import { gate } from "@warlock.js/access";
import { router } from "@warlock.js/core";
router.post("/orders", createOrderController, {
middleware: [authMiddleware([]), gate("orders.create")],
});

A user whose roles don’t grant orders.create gets a 403 before createOrderController ever runs.

2. Assert on a specific record (instance-level)

Section titled “2. Assert on a specific record (instance-level)”

A route gate can only ask “can they create orders at all?”. To enforce “can they update this order?”, register a policy in the orders module and assert in the service after loading the row:

src/app/orders/policies/index.ts
import { definePolicy } from "@warlock.js/access";
definePolicy("orders.update", (user, order) => order.get("customer_id") === user.id);

Load it from the orders module’s main.ts (import "./policies";) so it registers at boot.

src/app/orders/services/update-order.service.ts
import { authorize } from "@warlock.js/access";
export async function updateOrder(user: Auth, orderId: string, changes: OrderChanges) {
const order = await Order.find(orderId);
// passes only if the user holds `orders.update` AND owns this order
await authorize(user, "orders.update", { resource: order });
return order.merge(changes).save();
}

For UI hints — which buttons to show — use the boolean can:

return {
...order.toJSON(),
canEdit: await can(user, "orders.update", { resource: order }),
canDelete: await can(user, "orders.delete", { resource: order }),
};

That’s the whole loop: middleware gates the action class, the service authorizes the specific resource. Everything else in these docs builds on it.