Skip to content

This is one of the biggest advantages of building flags on Bridge instead of in isolation: Bridge already knows who’s signed in, what role they have, and what their workspace (called a tenant in the API) is paying for. On a backend, though, there’s one honest difference from the frontend worth stating up front. In a browser SDK, if Bridge auth is enabled, user.role and tenant.plan are already in every evaluation with no app code. A NestJS process isn’t a signed-in user, so nothing about role or plan reaches the evaluator automatically; you wire it in once, with an attribute provider that reads your already-verified JWT claims.

That’s actually the safer default: the value can only come from a source you trust, never from something a client handed you.

AuthAttributeProvider (from @nebulr-group/bridge-auth-core) takes a synchronous getClaims() callback and flattens the returned JWT claims into targeting attributes. Register it once at bootstrap on the underlying bridge instance:

import { AuthAttributeProvider } from '@nebulr-group/bridge-auth-core';
import { BridgeFlagsService } from '@nebulr-group/bridge-nestjs/flags';

// once at bootstrap, e.g. in an OnModuleInit
this.flags.bridge.registerAttributeProvider(
  new AuthAttributeProvider({ getClaims: () => getCurrentClaims() }),
);

The provider is consulted synchronously on every eval, so getClaims() must return the current request’s decoded claims (or undefined when there are none). It maps claims into attributes like this:

| Attribute | Claim | Example values | |---|---|---| | user.id | sub | the signed-in user’s id | | user.role | role | MEMBER, ADMIN, OWNER (or your custom roles) | | user.email | email | jane@acme.com | | tenant.id | tid | the current workspace’s ID | | tenant.plan | plan | FREE, PRO, ENTERPRISE | | privileges | privileges | the signed-in user’s privilege list |

Auth-derived attributes (user.id, user.role, user.email, tenant.id, tenant.plan, privileges) aren’t prefixed: a targeting rule references user.role, tenant.plan, and so on, exactly as it would against a Bridge frontend. Billing-derived attributes are prefixed, under bridge:billing.*. The provider itself counts as bridge-managed, so any dev-supplied attribute you pass on a flag() call wins on key collision, and Control Center surfaces the collision on the flag detail page.

getClaims() must read from a server-verified source (req.bridgeUser or req.bridgeApiToken, populated by BridgeAuthGuard) and never from anything a client supplied. This is the same “never trust client-sent role/plan attributes” rule as forwarded context: a browser can put any role in the x-bridge-context header, so plan/role targeting has to resolve from your own JWT, not from the wire.

With the provider registered, an admin builds the rule once in Control Center (your admin dashboard at app.thebridge.dev): on for users matching user.role equals ADMIN. Your gated route just works, with no attribute-passing code at the call site:

import { Controller, Get, UseGuards } from '@nestjs/common';
import { BridgeAuthGuard } from '@nebulr-group/bridge-nestjs';
import { RequireFlag, BridgeFlagGuard } from '@nebulr-group/bridge-nestjs/flags';

@Controller('billing-settings')
@UseGuards(BridgeAuthGuard, BridgeFlagGuard) // auth first, so claims are populated
export class BillingSettingsController {
  @Get()
  @RequireFlag('billing_settings')
  get() { /* … */ }
}

Same pattern for plan-gating a premium feature. Rule: on for users matching tenant.plan equals ENTERPRISE:

@Get('export')
@RequireFlag('export_reports')
exportReports() { /* … */ }

If you’re gating something billing already grants access to, prefer targeting a billing entitlement over the raw plan name; it survives plan renames and custom per-workspace grants. See Lock features to a plan.

When to reach for the on-demand flag path instead

Section titled “When to reach for the on-demand flag path instead”

The main @nebulr-group/bridge-nestjs entry point also ships an older @RequireFeatureFlag / FeatureFlagService that evaluates over the Bridge API, keyed on the caller’s access token. Because Bridge decodes that token itself, role and plan targeting resolve with no provider wiring. If a role-targeted rollout is all you need and you don’t want a live flags client, that path is less setup. See Gate features by role or privilege for the full comparison.