Skip to content
Back to skills

Dms Auth

ASecurity

Wires authentication and RBAC permissions on the AntelopeJS DMS. Use when protecting a route with @AuthUser / @AuthUserWithPermission, gating a page via its PageController options, registering permissions, or working with the user, role, and tenant models under @antelopejs/interface-dms/auth and @antelopejs/interface-dms.

  • 16 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 5, 2026
developmentgoapidatabasebackendsecurity

Works with

  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned November 5, 2026

npx -y skills add AntelopeJS/dms --skill dms-auth --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Dms Auth?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Dms Auth
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/antelopejs-dms-auth/badge)](https://www.skillsdirectory.com/skills/antelopejs-dms-auth)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: dms-auth
description: Wires authentication and RBAC permissions on the AntelopeJS DMS. Use when protecting a route with @AuthUser / @AuthUserWithPermission, gating a page via its PageController options, registering permissions, or working with the user, role, and tenant models under @antelopejs/interface-dms/auth and @antelopejs/interface-dms.
icon: i-ph-lock-key
category: security
tags: [auth, permissions, rbac, users, roles, tenants]
---

# Auth & permissions

Authentication answers "who is calling"; permissions answer "may they see or do this". Depth:
`docs/03.auth-and-tenancy/02.auth-and-permissions.md` (gating + RBAC) and
`01.authentication.md` (shipped flows: JWT rotation, invites, 2FA, recovery). The surface is
split across `@antelopejs/interface-dms/auth` (identity decorators),
`@antelopejs/interface-dms/guards` (permission & tenant guards), and `@antelopejs/interface-dms/permissions`
(permission registry).

## Protecting routes

Auth is applied with **parameter decorators** on controller route methods — each injects the
resolved user (or rejects if the requirement fails):

```ts
import { Controller, Get } from "@antelopejs/interface-api";
import { AuthUser, IfAuthUser } from "@antelopejs/interface-dms/auth";
import { AuthUserWithPermission } from "@antelopejs/interface-dms/guards";
import { User } from "@antelopejs/interface-dms/auth/db";

export class ThingController extends Controller("/api/things") {
  @Get("")
  async list(@AuthUser() user: User) { /* requires a logged-in user, else 401 */ }

  @Get("/public")
  async maybe(@IfAuthUser() user: User | undefined) { /* optional: undefined if anonymous */ }

  @Get("/admin")
  async admin(@AuthUserWithPermission(SomePageController) user: User) { /* requires permission, else 403 */ }
}
```

From `@antelopejs/interface-dms/auth`: `@AuthUser()` (require login), `@IfAuthUser()` (optional), `@AuthRawUser()`,
`@AuthOwnerOnly()` (platform owner only). From `dms/guards`: `@AuthUserWithPermission(target)`
(permission tied to a page/component), `@AuthTenantOwner()`, `@AuthTenantMember()`.

**`@IfAuthUser()` does not 401 on an expired token** — it silently treats the caller as
anonymous and returns a normal 200. If a page must hard-fail for expired sessions (so a
refresh interceptor fires), use `@AuthUser()` and give anonymous-allowed paths their own
route. This optional-auth-returns-200 behavior has caused a real token-refresh bug.

## Protecting pages

A page's auth comes from its `PageController` options (see **dms-pages**): `publicAccess:
true` (no auth), `authOnly: true` (login only), `memberAccess: true` (every signed-in member
holds the page, its components and actions without a role grant), default (login **and** the auto-derived page
permission), `permission: …` (a specific `Partial<Permission> | Action`),
`noComponentPermissions: true` (skip per-component permission derivation). Routes under a
controller that a page or `DataController` wraps **inherit** its auth — don't re-add login
checks; only add finer ones (ownership, a specific permission).

## Permissions & RBAC

Permissions are **hierarchical and auto-derived** from the page → component → action tree.
A component's permission id is the page's **`fullId`** (category chain + page id) followed by
the component key, then the action (e.g. `pages.recipes.submit-recipe.form.delete`), registered
automatically when the page loads. The roles editor titles each one with the component's metadata: DMS blocks set
their own, and a `CustomComponent` needs `.meta({ name, description, icon })` with `$` keys (see
**dms-best-practices**). Users hold roles; roles hold granted permission ids; the
platform **owner** holds everything. **Module pages are special**: prefixed
`modules.<moduleId>.…`, never grantable in the Roles tree, owner-only regardless of grants.

To contribute a standalone permission, register it in `construct`:

```ts
import { RegisterPermission } from "@antelopejs/interface-dms/permissions";

RegisterPermission("cookbook.edit", {
  id: "cookbook.edit",
  title: "$dms_cookbook.perm.edit",   // i18n key — add to every locale file
  icon: "i-ph-pencil",
  defaultGranted: false,
  // dependencies: ["cookbook.view"],  // enforced by HasPermission; granted with it by the roles editor and on save
});
```

Then gate with `@AuthUserWithPermission(...)` or the page/component permission options. To
check imperatively, use `dms/permissions`: `GetEffectiveUserPermissions`, `HasPermission`,
`GetPermission` / `GetPermissions` (`GetUserPermissions` for the base set). Two extension
points reshape the model: `RegisterPermissionsResolver` (`dms/permissions-resolver`) rewrites
a user's effective set (the role preview also runs it, with `context.preview` set on the
resolver's fourth argument and the viewer's user id: shape the set as for an ordinary
member there, never from the viewer's standing), and `RegisterTenantAccessGate` (`dms/tenant-access`, checked via
`CheckTenantAccess` / `AssertTenantAccess`) can block a whole tenant. The `dms/guards`
decorators also run the tenant access gate; routes that must stay reachable for a denied
tenant (billing, recovery) opt out with `@AuthTenantOwner({ bypassTenantAccessGate: true })`.

## Users, roles, tenants

Models are exposed as interfaces: `UserModel` and `SessionModel` under
`@antelopejs/interface-dms/auth/db` (the `User` entity type — what `@AuthUser()` resolves to — lives
there too); `RoleModel`, `UserInviteModel`, `TenantModel`, `TenantMemberModel` under
`@antelopejs/interface-dms/db`. Tenant lifecycle events are observable through the DMS hooks — see
`docs/02.building/06.backend-services.md`.

The DMS is **multi-tenant by default** (single-tenant apps live in the `default` tenant).
Primitives, covered in `docs/03.auth-and-tenancy/03.multi-tenant.md`: `@TenantScopedModel`
(`dms/tenant-scoped-model`) scopes a model to the request's tenant, `getRequestTenantId`
(`dms/request-tenant`) reads it (the verified credential's tenant, `default` without one), `applyTenantOwnership`
(`dms/tenant-ownership`) upserts a user's tenant membership (roles + owner flag),
`inviteUserToTenant` (`dms/invites`, expiry `INVITE_EXPIRY_DAYS`), and `CROSS_INSTANCE`
(`@antelopejs/interface-database`) for cross-tenant queries. SaaS mode (Stripe billing,
public registration, plans gating permissions) is `docs/03.auth-and-tenancy/04.saas-mode.md`.

Files in this skill

  • SKILL.md6.3 KB
  • cases.json2.2 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.