Policies & Guards
Elur Auth provides policy-based authorization for fine-grained access control.
# Creating a policy
Policies evaluate whether a user can perform an action:
import { createAuth, createPolicy } from "@elurjs/auth";
const adminPolicy = createPolicy({
evaluate: (user, action) => {
if (!user) return { allow: false, redirect: "/login" };
if (action === "admin") {
return user.roles?.includes("admin") ?? false;
}
return true;
},
});
auth.attachPolicy(adminPolicy);# can() and authorize()
# can(action, ctx?)
Returns a Signal<boolean> for reactive permission checks:
// In a template:
html`<button disabled=${() => !auth.can("admin").value}>Admin action</button>`;
// In an effect:
effect(() => {
if (auth.can("edit", { resource: "post-1" }).value) {
// enable edit mode
}
});# authorize(action, ctx?)
Returns a Signal<{ allow: boolean, redirect?: string }> with the full
policy decision including a redirect URL:
effect(() => {
const decision = auth.authorize("admin").value;
if (!decision.allow && decision.redirect) {
router.navigate(decision.redirect);
}
});# Multiple policies
Attach multiple policies — they're evaluated in order:
const rolePolicy = createPolicy({
evaluate: (user, action) => {
if (!user) return { allow: false, redirect: "/login" };
return user.roles?.includes(action) ?? false;
},
});
const ownershipPolicy = createPolicy({
evaluate: (user, action, ctx) => {
if (action === "edit" && ctx?.ownerId !== user?.id) {
return { allow: false, redirect: "/forbidden" };
}
return true;
},
});
auth.attachPolicy(rolePolicy);
auth.attachPolicy(ownershipPolicy);# Route guards (Elur Kit)
In Elur Kit, use middleware to protect routes:
// src/middleware.ts
import { defineMiddleware } from "@elurjs/kit";
export default defineMiddleware(async (ctx, next) => {
if (ctx.path.startsWith("/login")) return next();
const session = await getSession(ctx.request);
if (!session) {
return Response.redirect(new URL("/login", ctx.request.url));
}
return next();
});# Route-level guards with the core router
Use beforeEnter guards with the core router:
import { createRouter } from "@elurjs/core";
import { auth } from "../stores/auth";
const requireAuth = (to: string) => {
if (!auth.isAuthenticated.peek()) return "/login";
return true;
};
const requireAdmin = (to: string) => {
if (!auth.can("admin").peek()) return "/forbidden";
return true;
};
const router = createRouter([
{ path: "/", component: () => new Home() },
{ path: "/login", component: () => new Login() },
{ path: "/dashboard", component: () => new Dashboard(), beforeEnter: requireAuth },
{ path: "/admin", component: () => new Admin(), beforeEnter: requireAdmin },
]);💡 Tip
Use peek() instead of .value in guards to avoid creating reactive
dependencies. Guards run once per navigation, not reactively.