ADR-0014: Multi-Account Profile Switcher
- Status: Implemented
- Date: 2026-06-19
Context
Enterprise users frequently operate across multiple user identities (e.g., personal vs. corporate accounts, administrative vs. standard accounts, or acting on behalf of different organizations/clients).
Historically, switching identities required logging out completely and signing in again, which creates friction and disrupts active work. We need a secure, developer-friendly mechanism to maintain multiple authenticated profiles within the same browser session manager, allowing the user to context-switch dynamically in the UI or remove individual profiles (in-session individual logout) without losing session context for other active accounts.
Additionally, we must establish a clear distinction between Multi-Account Profile Switching and User Impersonation, which are often conflated but have different authentication constraints, security boundaries, and audit trail requirements.
Decision
Implement an internalized Multi-Account Profile Switcher in the core framework BFF and UI shell, and explicitly define the boundary separating it from administrative impersonation.
1. The Multi-Account Session Model
The browser session cookie contains only an opaque session manager ID. The actual account metadata resides server-side in a signed and encrypted session record containing:
active_account_id: Pointer to the currently active profile.accounts: A dictionary of authenticated sub-sessions representing verified profiles.
2. Feature Gating
To protect applications that do not require multi-tenancy, the feature is disabled by default:
ENABLE_PROFILE_SWITCHER=False(default).- When disabled, any profile switcher or merge endpoints (such as
POST /auth/switch-profileorPOST /auth/remove-profile) reject immediately with403 Forbidden(PermissionDeniedException), and switcher menus are hidden in the UI.
3. Clear Distinction from Impersonation
The framework separates profile switching from user impersonation along the following dimensions:
| Dimension | Multi-Account Profile Switcher | User Impersonation ("Run As" / "Sudo") |
|---|---|---|
| Authentication Proof | Mandatory. The user must have successfully authenticated (using passwords, OIDC callback, or magic links) for every profile in the session. | Administrative delegation. An administrator or privileged user assumes another identity without knowing or verifying their credentials. |
| Session Binding | All profiles are tied under a single server-side session manager, sharing a cookie and an absolute timeout. | A separate temporary impersonation token or sub-session is issued, scoped to the admin session and usually short-lived. |
| Audit Trails | Logged under the active profile's own user identity as the direct actor of the transaction. | Dual-actor logs. Audit logs must log both the impersonator (actor/operator) and the target user (subject/context) for non-repudiation. |
| Security Scope | Full access to the selected account's data, subject to standard ACLs/RBAC. | Scoped privileges. Security overrides prevent the impersonator from performing sensitive operations (e.g. changing passwords, configuring MFA, or signing documents). |
4. Same-Domain Custom Login Integration
For deployments acting as OIDC Identity Providers (like M-OIDC), the backend handles client prompts:
- When a client sends
prompt=select_accountor an intervention is needed, the authorization server redirects to the custom login route with the intervention contextpending_action: "select_account". - The custom page is responsible for rendering the custom chooser UI, while the framework exposes
/auth/switchable-usersand/auth/switch-profileAPIs to execute the selections securely.
5. In-Session Individual Profile Removal (Logout)
Implement Pattern 1: In-Session Individual Profile Removal:
- Users can log out of a single profile without destroying the entire session.
- Active Profile Swap: If the removed profile is the active one and other profiles remain, the system automatically redirects and switches context to another remaining profile in the session.
- Session Termination: If the last profile is removed, the server destroys the session manager record entirely and issues a cookie-clearing header.
- Inactive Profile Invalidation: If the removed profile was inactive (in the background), the React Query cache key
["switchable-profiles"]is invalidated in the UI to update the list dynamically without reloading the page.
Consequences
Positive
- Improved UX: Frictionless context switching and account adding.
- Security Boundaries: In-session individual profile removal ensures users can safely clean up specific profiles from public/shared devices.
- Audit Compliance: Because profile switching requires complete authentication, audit trails remain simple and map 1:1 with the active account context, preventing the tracking complexity associated with impersonation.
Negative
- Session Lifecycle Complexity: Session stores must handle dictionary-style schemas and check off sub-sessions during idle/absolute timeouts.
- CSRF & State Token Overhead: Adding an account via external OIDC callbacks requires strict validation of session-bound
stateparameters to prevent malicious profile insertion.