Skip to content

A role is a named set of privileges: scoped permission keys like USER_READ or TENANT_WRITE. Every user is assigned exactly one role per workspace (called a tenant in the API); the role determines what that user can do in that workspace.

Roles are fully custom to your app; you’re not stuck with a fixed enum. Every app starts with:

  • OWNER: required, protected, granted automatically. See The owner role.
  • ADMIN: created by default but just a normal role; rename it, change its privileges, or delete it.

From there you can define as many roles as you need. See Common role setups for a worked example, including a bespoke role for a specific client.

Both travel in the verified JWT, decoded onto the request by BridgeAuthGuard:

ClaimEnds up onType
roleuser.role (via @CurrentUser())string | undefined
privilegesuser.privileges (via @CurrentUser())string[] | undefined
privileges (API token)req.bridgeApiToken.privilegesstring[]
import { Controller, Get } from '@nestjs/common';
import { CurrentUser, BridgeUser } from '@nebulr-group/bridge-nestjs';

@Controller('users')
export class UsersController {
  @Get('me')
  getProfile(@CurrentUser() user: BridgeUser) {
    return { role: user.role, privileges: user.privileges };
  }
}

There is no server-side lookup involved; the guard never queries a roles database. Whatever role/privileges are embedded in the token are the role/privileges for that request. See How the token is kept current for what that implies when a role changes mid-session.

What a role can do is only true in the default setup; every app can change it. Read the app’s real roles and privileges (list_roles / bridge role list) before writing a rule.

Two mechanisms, for two kinds of caller:

MechanismApplies toWhat decides
@RequireFeatureFlag(key) (or a route rule’s featureFlag)A signed-in person (user JWT)The flag’s rule, e.g. privileges contains "USER_WRITE"
@RequirePrivilege(privilege)API tokens only; checks req.bridgeApiToken.privilegesThe token’s scope. It is not a gate on a person, so a user JWT is not checked against it

In practice: gate what a signed-in person can do with a flag ruled on a privilege, and scope what a token (script, integration, CI job) can do with @RequirePrivilege(). Prefer a privilege rule over a role rule; write user.role eq "ADMIN" only when you mean the role itself. contains is exact membership. See Gate with feature flags and API tokens.

import { Controller, Get, UseGuards } from '@nestjs/common';
import { BridgeAuthGuard, RequireFeatureFlag, CurrentUser, BridgeUser } from '@nebulr-group/bridge-nestjs';

@Controller('admin')
@UseGuards(BridgeAuthGuard)
@RequireFeatureFlag('admin-area') // rule: privileges contains "USER_WRITE"
export class AdminController {
  @Get('dashboard')
  getDashboard(@CurrentUser() user: BridgeUser) {
    return { message: 'Admin dashboard', admin: user.email };
  }

  @Get('settings')
  @RequireFeatureFlag('admin-settings') // rule: privileges contains "TENANT_WRITE"
  getSettings(@CurrentUser() user: BridgeUser) {
    return { settings: 'sensitive data' };
  }
}

Route-level decorators override controller-level ones; the guard uses Nest’s Reflector.getAllAndOverride, so the most specific decorator wins. A refused request gets 403 FEATURE_NOT_PERMITTED (or FEATURE_OFF) naming the flag.

For anything that must be enforced (not just hidden in a caller’s UI), put the flag on the actual endpoint too; the same rule answers the same way in the browser and the backend.