Skip to content

There’s no store to read and no token to fetch yourself. bridge.auth() / bridge.protect(...) verify the incoming Authorization: Bearer header (or x-api-key) and attach the decoded result to the Express Request before your handler runs. See Route guards for when that happens in the request lifecycle.

Section titled “The recommended path: req.bridgeUser and req.bridgeTenant”
import { Router } from 'express';

const router = Router();

router.get('/items', (req, res) => {
  const user = req.bridgeUser!;
  const tenant = req.bridgeTenant;
  res.json({ requestedBy: user.email, tenant: tenant?.name });
});

export default router;

Bridge Express augments the Express Request type via declaration merging, so req.bridgeUser, req.bridgeTenant, req.bridgeAccessToken, and req.bridgeApiToken are all typed and available without extra setup. req.bridgeUser and req.bridgeTenant only ever hold a value on a user JWT request; an API-token-only request has neither set, since API tokens don’t carry a user or workspace identity the same way (see API tokens).

BridgeUser is populated straight off the verified JWT claims. There’s no separate “minimal identity” vs. “full profile” split the way a frontend often has (one lean reactive object plus a richer profile fetch); this one object already carries everything decoded from the token:

FieldTypeDescription
idstringUser ID (sub claim)
emailstringEmail address
emailVerifiedbooleanEmail verification status
usernamestringUsername (preferred_username, falls back to email)
fullNamestringFull display name
givenName / familyNamestring | undefinedFirst / last name
localestring | undefinedUser’s locale
onboardedboolean | undefinedWhether onboarding is complete
tenantIdstringID of the workspace this token was issued for (a workspace is called a tenant in the API)
appIdstring | undefinedApp ID the token was issued for (aid claim)
scopestring | undefinedOAuth scopes on the token
rolestring | undefinedUser’s role in this workspace
privilegesstring[] | undefinedPrivilege keys from the JWT privileges claim
multiTenantAccessboolean | undefinedWhether the user can access more than one workspace

BridgeTenant is similarly decoded from the token’s tenant claims (tid/tenant_id, tenant_name, tenant_locale, tenant_logo, tenant_onboarded):

FieldTypeDescription
idstringWorkspace (tenant) ID
namestringWorkspace name
localestring | undefinedWorkspace locale preference
logostring | undefinedWorkspace logo URL
onboardedboolean | undefinedWhether the workspace has completed onboarding

If the token carries no tenant claim, transformJwtToBridgeTenant returns null and req.bridgeTenant is undefined.

req.bridgeApiToken is the counterpart on the machine-to-machine path, set when the request authenticated via x-api-key instead of (or alongside) a user JWT:

router.get('/api/status', bridge.protect(), (req, res) => {
  res.json({
    user: req.bridgeUser,          // set on the user-JWT path
    apiToken: req.bridgeApiToken,  // set on the API-token path; see API tokens
  });
});
FieldTypeDescription
substringToken subject identifier
appIdstringApp ID the token was issued for
tenantIdstring | nullWorkspace (tenant) ID; null for app-level tokens
type'api'Always 'api' for API tokens
privilegesstring[]Privilege strings (e.g. ['USER_READ', 'TENANT_WRITE'])
expnumber | undefinedExpiry (epoch seconds)

Both a user JWT and an API token can be present and valid on the same request at once; see API tokens for dual-credential requests.

The raw access token: req.bridgeAccessToken

Section titled “The raw access token: req.bridgeAccessToken”

You almost never need this either, but when your handler needs to call another Bridge-aware backend on the caller’s behalf (a downstream service, or Bridge’s own API), forward the verified token instead of re-deriving credentials:

router.get('/items/from-service-b', async (req, res) => {
  const data = await bridge.http.get('http://service-b/items', req.bridgeAccessToken);
  res.json(data);
});

req.bridgeAccessToken is only set on the user-JWT path (it’s the exact bearer token the middleware just verified). This is the backend counterpart to a frontend tokenStore, except there’s no refreshing to think about: your handler only ever sees a token that has already been validated for this request, and you’re not responsible for its lifetime.

Tenant-scoped data beyond the JWT: bridge.fromJwt()

Section titled “Tenant-scoped data beyond the JWT: bridge.fromJwt()”

req.bridgeTenant only gives you what’s baked into the JWT (id, name, locale, logo, onboarded). For subscription plan, entitlements, or branding, use bridge.fromJwt(req.bridgeAccessToken!), which fetches (and short-TTL-caches) a fuller snapshot for the token’s workspace:

router.get('/billing/plan', async (req, res) => {
  const tenant = bridge.fromJwt(req.bridgeAccessToken!);
  res.json(await tenant.subscription);
});

See How the token is kept current for how fresh this snapshot is relative to the JWT-decoded fields, and Multi-tenancy for how tenant scoping is enforced.