Protect a route with permissions
Decide who may do what to which record with access, and refuse the rest with a 403.
Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.
Stability The promise an interface ships with.
- Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
- Beta Usable in production; may change before it becomes stable.
- Experimental May change or go away; needs an explicit opt-in in main.yaml.
You will define roles in main.yaml, check a request with access, and see a refusal explained. It takes about 15 minutes.
You need
- A route that already knows the user (see Sign-in and sessions).
1. Declare roles
access:
roles:
admin: ["*"]
member: [orders.read]
owner: [orders.update]An admin may do anything. A member may read orders. The owner of an order may update it, because its owner equals the subject's id.
2. Check before you act
import { access, auth } from "mainsdk";
// your own order store
declare function loadOrder(id: string): Promise<{ type: string; id: string; owner: string }>;
declare function saveOrder(order: { id: string }, patch: unknown): Promise<unknown>;
export async function PATCH(req: Request, ctx: { params: Promise<{ id: string }> }) {
const session = await auth.require(req);
const order = await loadOrder((await ctx.params).id); // { type: "order", id, owner, ... }
const user = { id: session.user, roles: ["member"] }; // roles come from tenancy membership when you use it
access.require(user, "orders.update", order); // throws MAIN-ACCESS-0001 (403) unless allowed
return Response.json(await saveOrder(order, await req.json()));
}Use await access.check({ subject, action, resource }) when you want the decision and not a throw.
3. Explain a refusal
import { access } from "mainsdk";
const user = { id: "u_1", roles: ["member"] };
const order = { type: "order", id: "o_1", owner: "u_2" };
const d = access.decide({ subject: user, action: "orders.update", resource: order });
const why = access.explain(d.id); // { decision, request }; decision.reasons names the ruleCheck it worked
A member who does not own the order gets 403, and d.reasons says no role or owner rule grants orders.update. The owner gets 200, with the reason owner of order may orders.update. Every decision is in the audit log with the policy version.
Next
Was this page helpful?