Standalone UI ("Fidelity Port") with Multiple Backends
Framework M's UI architecture strictly decouples the Context Port (business logic, entity hierarchy, and permissions) from the Fidelity Port (the visual rendering layer, design system, and user interaction).
Because @framework-m/ui and @framework-m/desk are standard, composable React/Tamagui packages, the frontend can run as a standalone "Fidelity Port" completely decoupled from a single monolithic backend. It can connect to standard Framework M instances, bespoke microservices, or external ERPs (like Frappe) unified behind an authenticating gateway.
This guide details the 4 Backend Integration Flavors available when running a standalone UI.
1. Backend Flavor 1: Standard Framework M Backend
Connecting the standalone UI directly to a Framework M backend supports two authentication mechanisms based on client device capabilities.
1A. Default Browser Model (Session Cookie)
This is the standard pattern used by the default Web Desk. It follows the Backend-For-Frontend (BFF) pattern:
- Login endpoint (
POST /api/v1/auth/login) issues anhttpOnly,Secure,SameSite=Laxsession cookie (Set-Cookie: session=...). - The browser automatically transmits the cookie on subsequent API calls (
credentials: "include"). - Security Profile: Highest security for web browsers. JavaScript cannot inspect or exfiltrate the session token, eliminating credential leakage from XSS vectors.
// Default browser setup in @framework-m/desk
import { AuthProvider } from "@framework-m/desk";
export const authProvider = new AuthProvider({
apiUrl: "https://api.example.com/api/v1",
// Relies on native browser cookie jar
});
1B. Bearer Token Model (Mobile / Non-Browser / IoT Devices)
When building a Fidelity Port for native mobile apps (React Native / Expo), embedded industrial touchscreens, or specialized IoT / kiosk devices, HTTP cookies are often unavailable, awkward to synchronize, or disabled:
- Login returns an explicit JWT access token:
{ "access_token": "eyJhbGci...", "token_type": "bearer" }. - The client passes the token explicitly in the
Authorization: Bearer <token>header. - Security Trade-off: Storing Bearer tokens in browser
localStorageorsessionStoragecarries XSS exfiltration risks. However, on native mobile and specialized IoT hardware, storing tokens in OS-level hardware-backed vaults (iOS Keychain, Android Keystore, or encrypted device storage) provides a safe, standard deployment model.
Backend Configuration (framework_config.toml):
Ensure the bearer strategy is enabled in your authentication chain:
[auth]
# Priority order: check Bearer header first, fallback to session cookie
strategies = ["bearer", "session"]
jwt_secret = "${JWT_SECRET}"
jwt_algorithm = "HS256"
Frontend Client Configuration:
import { createForwardAuthProvider } from "@framework-m/desk";
import * as SecureStore from "expo-secure-store"; // Or native keychain
export const mobileAuthProvider = createForwardAuthProvider({
whoamiEndpoint: "https://api.example.com/api/v1/auth/me",
tokenHeaderName: "Authorization",
getToken: async () => {
const token = await SecureStore.getItemAsync("access_token");
return token ? `Bearer ${token}` : null;
},
loginUrl: "/login",
logoutUrl: "/logout",
});
2. Backend Flavor 2: Framework M Backend with Vouch Cookie
In enterprise federated environments, authentication is offloaded to a gateway like Vouch Proxy (or Traefik ForwardAuth, Nginx auth_request, OAuth2-Proxy).
- The user logs in via Vouch Proxy against an upstream IdP (Keycloak, Okta, Google).
- Vouch sets a domain cookie (e.g.
VouchCookie). - The reverse proxy validates the cookie and injects identity headers before forwarding to Framework M:
X-Vouch-User: user@example.comX-Vouch-IdP-Claims-Roles: Manager, Auditor
Backend Configuration (framework_config.toml):
Configure Framework M to trust reverse-proxy headers via HeaderAuthAdapter:
[auth]
strategies = ["header"]
[auth.header]
header_map = { user_id = "x-vouch-user", email = "x-vouch-user", roles = "x-vouch-idp-claims-roles" }
trusted_proxies = ["10.0.0.0/8", "172.16.0.0/12"]
Frontend Configuration:
import { createForwardAuthProvider } from "@framework-m/desk";
export const authProvider = createForwardAuthProvider({
whoamiEndpoint: "/api/v1/auth/me",
loginUrl: "https://vouch.example.com/login?url=https://app.example.com",
logoutUrl: "https://vouch.example.com/logout?url=https://app.example.com",
});
3. Backend Flavor 3: Bespoke API with Vouch Cookie
A standalone Framework M UI can connect to arbitrary custom backend services (written in Go, Python FastAPI/Litestar, or Node.js) sitting behind Vouch Proxy.
- The gateway handles authentication and injects user claims into downstream requests.
- The bespoke service exposes a lightweight session endpoint (e.g.
GET /api/whoami). - Business data is queried via standard Refine data providers.
Frontend Configuration:
import { createForwardAuthProvider } from "@framework-m/desk";
import type { DataProvider } from "@refinedev/core";
// 1. Auth Provider bound to the bespoke service's session endpoint
export const authProvider = createForwardAuthProvider({
whoamiEndpoint: "/api/whoami",
loginUrl: "https://vouch.example.com/login",
logoutUrl: "https://vouch.example.com/logout",
});
// 2. Custom DataProvider querying the bespoke API
export const customDataProvider: DataProvider = {
getList: async ({ resource, pagination, filters }) => {
const response = await fetch(`/api/${resource}?page=${pagination?.current || 1}`, {
credentials: "include", // Transmits VouchCookie
});
const data = await response.json();
return { data: data.items, total: data.total };
},
getOne: async ({ resource, id }) => {
const response = await fetch(`/api/${resource}/${id}`, { credentials: "include" });
return { data: await response.json() };
},
// ... implement create, update, delete as needed
};
4. Backend Flavor 4: Frappe Backend (Vouch Cookie or Upstream IdP Bearer)
You can use @framework-m/ui or @framework-m/desk as a modern, high-density frontend in front of an existing Frappe ERP backend. Depending on whether your Fidelity Port runs in a web browser or on a native mobile device, you have two authentication integration paths:
4A. Web Browsers: Vouch Cookie via vouch_frappe_auth
When users access the standalone UI via a desktop or mobile web browser behind a reverse proxy gateway (Vouch Proxy / Traefik):
- Install
castlecraft/vouch_frappe_authon your Frappe bench:bench get-app https://github.com/castlecraft/vouch_frappe_authbench --site your-site.local install-app vouch_frappe_auth - The app registers an
auth_hookin Frappe (hooks.py):- When an HTTP request reaches Frappe, the hook inspects
HTTP_X_VOUCH_USER. - It matches the email or username with an existing
Userrecord in Frappe and initializes the authenticated session automatically without requiring Frappe's native login screen.
- When an HTTP request reaches Frappe, the hook inspects
4B. Mobile & Native Clients: Upstream IdP Bearer Tokens via castlecraft/cfe
On native mobile apps (React Native / Expo) or specialized field devices, managing reverse-proxy cookie handshakes is often awkward. Mobile clients authenticate directly with an upstream Identity Provider (Keycloak, Auth0, Okta, Authentik) using standard OAuth2/OIDC PKCE flows and obtain a JWT access token.
To allow Frappe to accept and validate these upstream IdP Bearer tokens directly:
- Install and configure
castlecraft/cfeon the Frappe bench. cfeenables Frappe to validate incomingAuthorization: Bearer <jwt_token>against the configured upstream IdP public keys / JWKS.- Frappe resolves the authenticated user context per request without needing browser session cookies.
Frontend Setup (@framework-m/desk & @framework-m/desk/frappe):
import { createForwardAuthProvider } from "@framework-m/desk";
import { frappeDataProvider, createFrappeDataProvider } from "@framework-m/desk/frappe";
// Option A: Browser Web using Vouch session cookie
export const frappeWebAuthProvider = createForwardAuthProvider({
whoamiEndpoint: "/api/method/frappe.auth.get_logged_user",
loginUrl: "https://vouch.example.com/login",
logoutUrl: "https://vouch.example.com/logout",
});
// Option B: Mobile App using upstream IdP Bearer token (via castlecraft/cfe)
import * as SecureStore from "expo-secure-store"; // Or native OS keychain
export const frappeMobileAuthProvider = createForwardAuthProvider({
whoamiEndpoint: "https://erp.example.com/api/method/frappe.auth.get_logged_user",
tokenHeaderName: "Authorization",
getToken: async () => {
const token = await SecureStore.getItemAsync("idp_access_token");
return token ? `Bearer ${token}` : null;
},
loginUrl: "/login",
logoutUrl: "/logout",
});
// Built-in Frappe Data Provider from @framework-m/desk/frappe
export const dataProvider = frappeDataProvider;
// Or configure with a custom API URL:
export const customFrappeProvider = createFrappeDataProvider({
apiUrl: "https://erp.example.com",
});
The @framework-m/desk/frappe subpath is a self-contained module providing:
frappeDataProvider: Out-of-the-box Refine integration with Frappe filter operator mapping (eq,ne,contains,in), pagination (limit_start/limit_page_length), sorting, andcustom()RPC calls.useFrappeDocType: Hook for dynamic client-side Frappe DocType schema discovery (/api/method/frappe.desk.form.load.getdoctype).FrappeListView&FrappeDocTypeView: Pre-built components that render dynamic Frappe forms and lists with Tamagui styles.
5. Architectural Comparison Matrix
| Integration Flavor | Authentication Mechanism | Target Environment | Key Security Advantage |
|---|---|---|---|
| 1A: Framework M (Session) | httpOnly, Secure cookie | Desktop & Mobile Web Browsers | Immune to browser XSS token exfiltration (BFF model). |
| 1B: Framework M (Bearer) | Authorization: Bearer <jwt> | Native Mobile (Expo), IoT, Hardware Kiosks | Flexible on headless hardware; stored in OS Keychains. |
| 2: Framework M (Vouch) | Domain cookie (VouchCookie) + X-Vouch-* headers | Corporate SSO / Federated Gateways | Centralized OIDC/SAML at gateway; zero backend auth code. |
| 3: Bespoke API (Vouch) | Domain cookie + Header claims | Custom Go/Python/Node microservices | Decouples business logic; unifies telemetry and data. |
| 4A: Frappe Backend (Vouch) | vouch_frappe_auth hook | Web Browser on Frappe / ERPNext | Transparent SSO at reverse proxy; zero backend login UI. |
| 4B: Frappe Backend (Bearer) | castlecraft/cfe (Upstream IdP Bearer) | Native Mobile Apps (React Native / Expo) | Direct IdP JWT validation; eliminates cookie friction on mobile. |