@realm-id/web-bff-realmid
v0.3.3
Published
Drop-in adapter preset wiring @realm-id/web to the realmid.dev reference BFF (Realm-ID/api).
Maintainers
Readme
@realm-id/web-bff-realmid — preset for the realmid.dev reference BFF
Drop-in adapter + gates + endpoints for @realm-id/web so it talks
correctly to Realm-ID/api (the canonical reference BFF,
formerly Realm-ID/bff-api; implements BFF-SPEC.md, with
realmid-specific extensions).
Why this exists
The reference BFF deviates from BFF-SPEC.md in six places — ones likely to recur for any partner whose backend predates the SPEC:
- snake_case wire shape (
session_token,expires_at, …). status: "ok" | "tenants_required"discriminator on/login.- Tokenless
/tokenrotation — server-side rotation in Redis;/tokenreturns only{ expires_at }and the SPA keeps the same opaque session-id bearer. - Flat
/meshape ({user_id, realm_id, tenant_id, role, email, display_name, expires_at}). - HTTP 412 MFA gate with
code: mfa_required | mfa_registration_requiredand anmfa_challenge_token. - HTTP 412 session-limit gate with
code: session_limit_reachedand a one-shotrevocation_token.
The preset wires those into the SDK's adapter + gate machinery in one import.
Usage
import { createRealm } from "@realm-id/web";
import { realmidBffPreset } from "@realm-id/web-bff-realmid";
const realm = createRealm({
baseUrl: "https://api.realmid.dev",
...realmidBffPreset(),
});
await realm.ready();
try {
await realm.login({ method: "google", providerToken: idToken });
} catch (e) {
if (e instanceof RealmError) {
if (e.code === "tenants_required") {
// realm.getState().pendingTenants is populated; show picker, then
// realm.switchTenant(...) → preset has switchTenant: null, so
// the SDK calls /login again with tenantId.
}
if (e.code === "mfa_required") {
// body.challengeToken / body.method
}
if (e.code === "session_limit_reached") {
// body.revocationToken / body.sessions
}
}
}What the preset configures
| Field | Value |
|---|---|
| adapters.login | {status, session_token, expires_at, user{id,email,display_name}, tenants[{id,role,display_name}]} → canonical |
| adapters.me | flat snake_case → {user, tenants:[{id:tenant_id, role}], currentTenantId, expiresAt} |
| adapters.token | {expires_at} → tokenless rotation |
| adapters.providers | snake_case provider list → camelCase |
| gates | 412 mfa_required, mfa_registration_required, session_limit_reached |
| endpoints.providers | /identity-providers |
| endpoints.switchTenant | null (falls back to /login with tenantId) |
| endpoints.mfaVerify | /auth/mfa/verify |
| refresh | { tokenless: true, sendBearer: true } |
| clientTypeQueryParam | platform (so realm.providers({clientType:"web"}) → ?platform=web) |
Any of these can be overridden by passing your own field after the spread:
createRealm({
baseUrl: ...,
...realmidBffPreset(),
endpoints: { ...realmidBffPreset().endpoints, mfaVerify: "/v2/mfa/verify" },
});Or use the lower-level exports — realmidBffAdapters, realmidBffGates,
realmidBffEndpoints — if you want to mix and match.
