Skip to main content

Authorization

The RapidREST's authorization system is a Roles Based Access Control (mRBAC) system allowing two primary methods for access protection.

@RequiresRole

The simplest form of authorization: require the authenticated user to have one of the given roles.

@RequiresRole('admin')
@Delete('/:id')
async deletePet(@Param('id') id: string) {
return this.petService.delete(id);
}

Requests from a user without a matching role are rejected before the handler runs.

@RequiresScope (route-level)

A coarser, token-level check, distinct from ACLs: require the authenticated user's JWT to carry one or more scopes before the handler runs at all. This is checked before any per-resource ACL check, so it's a cheap way to reject requests from tokens that were never issued the right scope, without touching the database.

@RequiresScope('pets:write')
@Delete('/:id')
async deletePet(@Param('id') id: string) {
return this.petService.delete(id);
}

Scopes live on JWTUser.scopes — set by whatever issued the token (for example, @rapidrest/auth's User.scopes, built at runtime rather than stored). ACLAction.FULL ("*") also satisfies any required scope.

This is unrelated to the property-level @RequiresScope documented in Models & Persistence, which hides individual fields from a response rather than gating an entire endpoint.

Access Control Lists & @Protect

When more fine-grained control is desired an Access Control List (ACL) can be used. ACLs protect resources with an Inheritance based list of mapped permissions to user IDs and roles. Each ACL contains a unique identifier of the resource to protect, a list of ACLRecord entries detailing the list of permissions for each associated role and/or user uid and an optional parent association when inheritance is desired.

Each ACLRecord grants a userOrRoleId an arbitrary list of actions — plain strings, not fixed boolean fields:

interface ACLRecord {
userOrRoleId: string;
actions: string[];
}

ACLAction is a predefined set of conventional action strings recognized by the built-in CRUD routes, not a closed enum — actions may contain any custom string beyond these (e.g. "publish") to express service-specific permissions:

const ACLAction = {
FULL: "*", // grants every action, including any not otherwise listed
COUNT: "count",
CREATE: "create",
DELETE: "delete",
EXISTS: "exists",
LIST: "list",
READ: "read",
TRUNCATE: "truncate",
UPDATE: "update",
};

Protected resources include:

  • URL paths - Protect individual API endpoints by URL pattern (supports regex)
  • Class types - Protect entire classes of data types (e.g. User or Pet)
  • Documents - Protect individual documents or records

To designate a resource as protected simply add the @Protect decorator to one of the following:

  • Route Class - Add the @Protect decorator to the route class to protect all endpoints for the base route path.
  • Endpoint Function - Add the @Protect decorator to a single endpoint function to protect a specific REST API endpoint.
  • Class Type - Add the @Protect decorator to a class definition to protect an entire class and its documents.

The following is an example of how to define an initial ACL for the class Pet. The permissions granted are strictly read-only.

@Protect(
{
uid: "Pet",
records: [
{
userOrRoleId: "anonymous",
actions: [ACLAction.READ, ACLAction.LIST],
},
{
userOrRoleId: ".*",
actions: [ACLAction.READ, ACLAction.LIST],
}
]
}
)
export class Pet extends BaseMongoEntity {
// ...

The above example only creates a single ACL for the entire class of Pet. Thus, any documents stored in the database will use this ACL. When it is desired to have an ACL per document of a given class, passing in true as the second argument to @Protect will enable document-level ACL protection. This is shown in the example for the class User below.

@Protect(
{
uid: "User",
records: [
{
userOrRoleId: "anonymous",
actions: [ACLAction.CREATE],
},
{
userOrRoleId: ".*",
actions: [],
}
]
},
true
)
export class User extends BaseMongoEntity {
// ...

In the above example for User the only permission granted is for unauthenticated users to be able to create new users (e.g. register a new account). When document-level permission is used, the user creating the document takes full ownership in the associated ACL. As a result, the above ACL would deny all access for existing user accounts to all others, except for the user itself.

Permission Mapping

When using the built-in CRUDRoute or ModelRoute base classes for build REST APIs in RapidREST, each permission (ACLAction) in the ACL maps to a single REST API action. The following tables describes each of those mappings.

FunctionHTTP Verb/PathPermissionScope
countHEAD /ACLAction.COUNTClass
createPOST /ACLAction.CREATEClass
deleteDELETE /:idACLAction.DELETEDocument
existsHEAD /:idACLAction.EXISTSDocument
findGET /ACLAction.LISTClass
findByIdGET /:idACLAction.READDocument
truncateDELETE /ACLAction.TRUNCATEClass
updatePUT /:idACLAction.UPDATEDocument
updateBulkPUT /ACLAction.UPDATEClass
updatePropertyPUT /:id/:propertyACLAction.UPDATEDocument

Optionally, you can always perform custom ACL permission checks using the ACLUtils utility from within your code.

@Inject(ACLUtils)
private aclUtils?: ACLUtils;

@Get("/:id")
public async getPetByID(@Param("id") id: string, @User user: JWTUser) {
const result: Pet | undefined = await this.repoUtils.findOne(id);
if (!result) {
throw new ApiError(ApiErrors.NOT_FOUND, 404, ApiErrorMessages.NOT_FOUND);
}

if (!(await this.aclUtils?.hasPermission(user, result.uid, ACLAction.READ))) {
throw new ApiError(ApiErrorMessages.AUTH_PERMISSION_FAILURE, 403, ApiErrorMessages.AUTH_PERMISSION_FAILURE);
}

return result;
}

Turning it off, and trusted roles

conf.defaults({
rbac: {enabled: true},
trusted_roles: ['admin'],
});

rbac:enabled is a global switch. Set it to false to bypass every ACL check (useful in tests). Users with any role listed in trusted_roles bypass ACL checks entirely, regardless of what the record's ACL says.