Skip to main content
The Bloom Housing API uses a combination of Role-Based Access Control (RBAC) and Attribute-Based Access Control (ABAC) implemented with Casbin. Roles are hierarchical — each role inherits all permissions of the roles below it.

Role hierarchy

From least to most privileged:
anonymous → user → partner → jurisdictionAdmin → supportAdmin → admin
Each role in the chain inherits all permissions of the roles below it. For example, a partner can do everything a user can do, plus additional listing and application management actions.

What each role can do

Unauthenticated (not logged in) requests.
  • Read (browse) all published listings
  • Submit new applications to listings
  • Read jurisdiction data
  • Read multiselect questions (preferences and programs)
  • Read agency data
Most read-only public-facing data is accessible without any credentials. You only need to authenticate to manage resources or access private data.

How permission checks work

Every protected endpoint runs two layers of guards before the request handler executes.

1. JwtAuthGuard

Verifies the access-token cookie contains a valid, unexpired JWT. If the token is missing or invalid, the request returns 401 Unauthorized before any permission check occurs. Endpoints using OptionalAuthGuard skip this rejection for unauthenticated requests — the user is simply treated as anonymous, and the request proceeds.

2. PermissionGuard

After identity is established, PermissionGuard checks whether the user’s role is permitted to perform the requested action on the requested resource type. The action is inferred automatically from the HTTP method:
HTTP methodPermission action
GETread
POSTcreate
PUT / PATCHupdate
DELETEdelete
Individual endpoints can override this mapping using a @PermissionAction decorator (for example, a POST endpoint that performs a read action). admin users bypass the permission check entirely and are granted access to all endpoints.

Attribute-based checks (ABAC)

For resource-level ownership checks — such as ensuring a user can only read or update their own application — Casbin evaluates attribute expressions at request time. For example, the policy rule for a user reading an application is:
p, user, application, r.sub == r.obj.userId, read
Here, r.sub is the authenticated user’s ID and r.obj.userId is the owner field on the loaded resource. If they don’t match, the request returns 403 Forbidden. This ABAC check runs inside the service handler after the resource is loaded from the database — not in a guard — because the resource must be fetched first.

Permission policy reference

The full policy is defined in permission_policy.csv. The format of each rule is:
p, <role>, <resource>, <attribute_expression>, <action_regex>
Role inheritance is declared with g lines:
g, admin, supportAdmin
g, supportAdmin, jurisdictionAdmin
g, jurisdictionAdmin, partner
g, partner, user
g, user, anonymous
Key policies by resource:
RoleAllowed actions
anonymousread
admin, supportAdminall actions
RoleConditionAllowed actions
anonymoussubmit
usersubmit
userr.sub == r.obj.userIdread
admin, supportAdminall actions
RoleConditionAllowed actions
anonymouscreate
userr.sub == r.obj.idread, update
adminall actions
RoleAllowed actions
anonymousread
jurisdictionAdmin, supportAdminread
adminall actions
RoleAllowed actions
adminall actions
Feature flag management is restricted to admin only — no other role can read or write feature flags via the API.
RoleAllowed actions
partnerread
jurisdictionAdmin and aboveall actions
adminall actions

Special-purpose guards

GuardPurpose
OptionalAuthGuardAllows both authenticated and unauthenticated requests. Authenticated users get their role; unauthenticated requests proceed as anonymous.
ApiKeyGuardChecks for a valid API_PASS_KEY header. Applied at the controller level on most endpoints as a first line of defense.
AdminOrJurisdictionalAdminGuardRestricts access to users who are either admin or jurisdictionAdmin.
UserProfilePermissionGuardEnforces that a user can only access or modify their own profile.

Build docs developers (and LLMs) love