diff --git a/apps/edr-freight-web/backoffice/src/App.tsx b/apps/edr-freight-web/backoffice/src/App.tsx
index 8eaf0d4e1..bd8eccc61 100644
--- a/apps/edr-freight-web/backoffice/src/App.tsx
+++ b/apps/edr-freight-web/backoffice/src/App.tsx
@@ -24,6 +24,7 @@ import BookingContractPage from "./pages/bookings/BookingContractPage";
import BookingRequestDetailPage from "./pages/bookings/BookingRequestDetailPage";
import BookingRequestsPage from "./pages/bookings/BookingRequestsPage";
import NewBookingPage from "./pages/bookings/NewBookingPage";
+import UserManagementHostPage from "./pages/dashboard/user-management/UserManagementHostPage";
import DemoUser1Page from "./pages/dashboard/demo/DemoUser1Page";
import DemoUser2Page from "./pages/dashboard/demo/DemoUser2Page";
import OverviewPage from "./pages/dashboard/OverviewPage";
@@ -298,6 +299,10 @@ const App = () => {
} />
} />
+ {/* iframe-based user management module */}
+ } />
+
+ {/* Legacy embedded user management routes */}
} />
} />
} />
diff --git a/apps/edr-freight-web/backoffice/src/auth/tokenManager.ts b/apps/edr-freight-web/backoffice/src/auth/tokenManager.ts
new file mode 100644
index 000000000..3ee581eaf
--- /dev/null
+++ b/apps/edr-freight-web/backoffice/src/auth/tokenManager.ts
@@ -0,0 +1,117 @@
+export interface TokenMessage {
+ type: 'UM_AUTH_TOKEN';
+ token: string;
+ refreshToken: string;
+}
+
+/**
+ * Manages authentication tokens received from parent window (host)
+ * Used by iframe modules to receive and store auth tokens
+ */
+class TokenManager {
+ private token: string | null = null;
+ private refreshToken: string | null = null;
+ private tokenResolve: ((token: string) => void) | null = null;
+ private tokenPromise: Promise;
+ private isFramed: boolean;
+
+ constructor() {
+ // Check if we're running in an iframe
+ this.isFramed = window.self !== window.top;
+
+ // Create a promise that resolves when token is received
+ this.tokenPromise = new Promise((resolve) => {
+ this.tokenResolve = resolve;
+ });
+
+ if (this.isFramed) {
+ this.setupMessageListener();
+
+ // Request token after 2 seconds if not received
+ setTimeout(() => {
+ if (!this.token) {
+ this.requestTokenFromHost();
+ }
+ }, 2000);
+ }
+ }
+
+ private setupMessageListener() {
+ window.addEventListener('message', (event) => {
+ // Security: Only accept from parent window
+ if (event.source !== window.parent) {
+ return;
+ }
+
+ const data = event.data as TokenMessage | undefined;
+
+ if (data?.type === 'UM_AUTH_TOKEN') {
+ this.token = data.token;
+ this.refreshToken = data.refreshToken;
+
+ // Store in localStorage for persistence
+ localStorage.setItem('um_auth_token', data.token);
+ localStorage.setItem('um_refresh_token', data.refreshToken);
+
+ console.log('✅ Token received from host');
+
+ // Resolve the promise
+ if (this.tokenResolve) {
+ this.tokenResolve(data.token);
+ }
+ }
+ });
+ }
+
+ private requestTokenFromHost() {
+ console.log('📤 Requesting token from host...');
+ window.parent.postMessage(
+ { type: 'UM_REQUEST_AUTH' },
+ window.location.origin
+ );
+ }
+
+ /**
+ * Get token - waits for it if not yet received
+ */
+ async getToken(): Promise {
+ if (this.token) {
+ return this.token;
+ }
+
+ // Check localStorage as fallback
+ const stored = localStorage.getItem('um_auth_token');
+ if (stored) {
+ this.token = stored;
+ return stored;
+ }
+
+ // Wait for token to arrive from parent
+ return this.tokenPromise;
+ }
+
+ /**
+ * Get token synchronously (returns null if not available)
+ */
+ getTokenSync(): string | null {
+ return this.token || localStorage.getItem('um_auth_token');
+ }
+
+ getRefreshToken(): string | null {
+ return this.refreshToken || localStorage.getItem('um_refresh_token');
+ }
+
+ clearTokens() {
+ this.token = null;
+ this.refreshToken = null;
+ localStorage.removeItem('um_auth_token');
+ localStorage.removeItem('um_refresh_token');
+ }
+
+ isInFrame(): boolean {
+ return this.isFramed;
+ }
+}
+
+// Export singleton instance
+export const tokenManager = new TokenManager();
diff --git a/apps/edr-freight-web/backoffice/src/pages/dashboard/user-management/UserManagementHostPage.tsx b/apps/edr-freight-web/backoffice/src/pages/dashboard/user-management/UserManagementHostPage.tsx
new file mode 100644
index 000000000..526667a31
--- /dev/null
+++ b/apps/edr-freight-web/backoffice/src/pages/dashboard/user-management/UserManagementHostPage.tsx
@@ -0,0 +1,113 @@
+import { useEffect, useRef, useState } from 'react';
+import { useNavigate, useLocation } from 'react-router-dom';
+import { getCookie } from '@/auth/cookies';
+
+function readToken(): string | null {
+ return getCookie('auth-token') ?? null;
+}
+
+function readRefreshToken(): string | null {
+ return getCookie('refresh-token') ?? null;
+}
+
+export default function UserManagementHostPage() {
+ const navigate = useNavigate();
+ const location = useLocation();
+ const iframeRef = useRef(null);
+
+ const mountBase = (
+ (import.meta.env.VITE_USER_MANAGEMENT_BASE as string | undefined) ?? '/_um'
+ ).replace(/\/$/, '');
+
+ const moduleOrigin = window.location.origin;
+
+ const [iframeSrc] = useState(() => {
+ const sub = location.pathname.replace(/^\/(?:dashboard\/)?um(?=\/|$)/, '');
+ return mountBase + (sub || '/') + location.search;
+ });
+
+ // ✅ Send token when iframe loads
+ const handleIframeLoad = () => {
+ const token = readToken();
+ const refreshToken = readRefreshToken();
+ const target = iframeRef.current?.contentWindow;
+
+ if (!token) {
+ console.warn('⚠️ No authentication token found');
+ return;
+ }
+
+ if (!target) {
+ console.warn('⚠️ No iframe reference');
+ return;
+ }
+
+ target.postMessage(
+ {
+ type: 'UM_AUTH_TOKEN',
+ token,
+ refreshToken,
+ },
+ moduleOrigin
+ );
+
+ console.log('✅ Token sent to iframe module');
+ };
+
+ // ✅ Listen for messages from iframe
+ useEffect(() => {
+ const onMessage = (event: MessageEvent) => {
+ // Security: Only accept from same origin
+ if (event.origin !== moduleOrigin) {
+ console.warn('🚫 Blocked message from different origin:', event.origin);
+ return;
+ }
+
+ const data = event.data as { type?: string; path?: string } | undefined;
+ if (!data) return;
+
+ // Handle auth request (if module asks for token again)
+ if (data.type === 'UM_REQUEST_AUTH') {
+ const token = readToken();
+ const refreshToken = readRefreshToken();
+ const target = iframeRef.current?.contentWindow;
+
+ if (token && target) {
+ target.postMessage(
+ {
+ type: 'UM_AUTH_TOKEN',
+ token,
+ refreshToken,
+ },
+ moduleOrigin
+ );
+ console.log('✅ Token resent to iframe (on request)');
+ }
+ return;
+ }
+
+ // Handle route synchronization
+ if (data.type === 'UM_ROUTE_CHANGED' && typeof data.path === 'string') {
+ const target = '/dashboard/um' + data.path;
+ if (window.location.pathname + window.location.search !== target) {
+ navigate(target, { replace: true });
+ }
+ }
+ };
+
+ window.addEventListener('message', onMessage);
+ return () => window.removeEventListener('message', onMessage);
+ }, [moduleOrigin, navigate]);
+
+ return (
+
+
+
+ );
+}