One platform, many apps
Most companies do not have one application. They have a product, an admin panel, a couple of internal tools, and each one usually grows its own login. The Bridge inverts that: apps are a first-class object on the platform. Each app registers with its own app ID and gets the full login stack, from social login and SSO federation (SAML and OIDC) to MFA and passkeys, configured per app from one Control Center. Adding auth to the next internal tool means registering the next app, not building another login.
The model: apps, workspaces, memberships
Three objects carry the whole architecture:
- An app is one of your applications on The Bridge.
- A workspace (tenant) is a customer organization inside an app.
- A membership connects a user to a workspace, and it is the membership that carries the role, team assignments, and enabled flag.
The consequence of putting the role on the membership instead of the user: the same person can be an admin in one workspace and a viewer in another, behind a single set of credentials. There is no duplicate account per organization, and there is no global role leaking across customers.
Switching workspaces is part of signing in
When a user with access to several workspaces signs in, workspace selection is a state of the login flow itself, not a screen you build. The frontend SDKs expose the whole thing: an auth:workspace-changed event fires on a switch, useTenantUsers() lists the workspaces available during selection, and hasMultiTenantAccess on the profile tells you whether to show a switcher at all. Because the switch happens inside the auth machinery, the session token is reissued for the new workspace: permissions and data scope flip together, atomically.
Your data, scoped by the token
The Bridge holds identity and workspace context. Your rows stay in your database, and the token tells you which workspace they belong to. Concretely: say your app stores projects, and Acme and Initech are both customers. Every project row carries the workspace it belongs to, and when someone at Acme creates a project, your endpoint reads the workspace from their verified token, never from the request body. A user cannot ask their way into another company's data, because the tenant ID is not something the client gets to say.
What that looks like depends on your backend framework. In NestJS, the SDK delivers the verified user through a decorator:
// Fetching the signed-in user is as easy as one decorator
@Controller('projects')
export class ProjectsController {
@Post()
async create(
@Body() data: CreateProjectDto,
@CurrentUser() user: BridgeUser,
) {
// tenantId comes from the verified JWT, not from the request body
return this.projectsService.create(data, user.tenantId, user.id);
}
}In Express, the middleware puts the same verified user straight on the request:
// Fetching the signed-in user is as easy as reading req.bridgeUser
router.post('/projects', async (req, res) => {
const user = req.bridgeUser!;
// tenantId comes from the verified JWT, not from the request body
const project = await projects.create(req.body, user.tenantId, user.id);
res.status(201).json(project);
});How strictly you separate data stays your call. The documented strategies range from a tenantId column, which fits most apps, to a schema per tenant, to a fully separate database per tenant for maximum isolation. The full pattern, per framework and with provisioning included, is in the multi-tenancy docs.
Workspaces provision themselves
A new customer signup should not involve a human. The Bridge sends webhooks when tenants and users are created (TENANT_CREATED, TENANT_USER_CREATED, and their update and delete siblings), so your backend can set up default data the moment a workspace appears. The documented pattern pairs webhooks with a just-in-time fallback: if a request arrives for a workspace you have not seen, create it on the spot. Between the two, provisioning has no gap.
The workspace is also the commercial unit
Subscription, entitlements, and branding hang off the same workspace object that auth manages. Server-side, one call gives you all of it: bridge.fromJwt(jwt) returns the current workspace's plan, an entitlements.can() check for feature gating, and its branding, cached and deduped per request. On the frontend, the TeamManagementPanel component gives every workspace its own user management inside your app: invites, roles, password resets, workspace settings.
This is the part that outlasts your current roadmap: an app built on this model is already shaped like a sellable product. Turning an internal tool into a commercial one means attaching a plan to the workspace, not rebuilding identity and billing.
