Overview
Loopar ships a complete authentication and authorization layer — no external auth library required. It covers users, roles, granular permissions, sessions, and CSRF protection out of the box.
| Piece | Responsibility |
|---|---|
| Auth | Login, logout, session lifecycle |
| PermissionManager | Resolves what a user is allowed to do |
| ActionScanner | Discovers controller actions and their access rules |
| PermissionSync | Keeps permission records in sync with the code |
| CSRF | Cross-site request forgery protection |
Passwords are never stored in plain text — Loopar hashes them with Argon2 via loopar.hash(plain).
Users & Sessions
The Session
Server-side code reads the current session through loopar.session:
import loopar from "loopar";
const userId = loopar.session.user; // current user id
const site = loopar.session.site; // current tenant
const isAdmin = loopar.session.is_admin; // administrator flag
if (!loopar.session.user) {
loopar.throw("Authentication required", 401);
}
Looking Up Users
// Fetch a user by id or email
const user = await loopar.getUser("user@example.com");
// Disable a user account
await loopar.disabledUser(userId);
Login & Logout
Authentication is handled by the framework's Auth layer. A successful login() establishes a session and sets a CSRF cookie; logout() kills the session. You normally don't call these directly — the built-in auth pages and API routes do.
Roles & Permissions
Loopar's authorization model is role-based with granular, per-document permissions.
Levels of Control
| Level | Example |
|---|---|
| Document | Who can read / write / delete Customer records |
| Field | Hide salary from non-HR roles |
| Action | Who can call actionApprove on an Invoice |
Checking Permissions
PermissionManager resolves whether a user may perform an action:
import { PermissionManager } from "loopar";
// Can this user delete Customer records?
const allowed = PermissionManager.can("Customer", "delete", username);
// All resolved permissions for a user
const perms = PermissionManager.getPermissions(username);
When permissions change, PermissionManager.invalidate(username) clears the cached set for that user.
Controller Actions & Access
Only methods prefixed with action on a controller are reachable over HTTP. Everything else stays private to the server.
import { BaseController } from "loopar";
export default class InvoiceController extends BaseController {
// Reachable: GET/POST /api/Invoice/approve
async actionApprove() { /* ... */ }
// NOT reachable over HTTP — internal helper only
recalcTotals() { /* ... */ }
}
ActionScanner walks every app and registers these actions so PermissionManager can guard them. Unauthenticated actions are surfaced through ActionScanner.getPublicActions().
Bypassing Permission Checks
Trusted server-side code can skip the permission check on a save:
await doc.save({ ignore_permissions: true });
Use this sparingly — only in code paths you fully control.
CSRF & Realtime
CSRF Protection
Every authenticated session carries a CSRF token, set as a cookie on login. State-changing requests must echo it back; the csrf module validates it before the request reaches your controller.
Realtime Authentication
Loopar's realtime layer (Socket.IO) authenticates connections with a JWT scoped to the tenant namespace, so realtime events respect the same tenant isolation as the rest of the framework.
Best Practices
- Never expose the dev site publicly — it is the admin control center for every tenant.
- Prefer
doc.save()overloopar.db.setValue()when permissions and hooks matter — the low-level DB API bypasses both. - Keep logic behind
action-prefixed methods soActionScannercan discover and guard it. - Hash secrets with
loopar.hash()— never store credentials or tokens in plain text. - Scope realtime events to the tenant namespace; don't broadcast across tenants.