Account switching: preserve the actor and change the buying context
Keep the signed-in person separate from the buying account, verify every switch, and reload the prices and cart that belong to the new context.
In this guide
- Authenticated actor
- Current permission
- Selected account
- Scoped operations
One person, several buying relationships
A purchasing manager signs in at a parent organization and selects a regional account. The storefront now needs that account's catalog, pricing, addresses, and permissions. It still needs to know which person signed in. Replacing the session user indiscriminately can erase the identity that should explain every subsequent action.
Represent the actor and the selected buying context separately. The actor answers who authenticated. The buying context answers which authorized organization and shopper relationship apply to this operation. Both matter when a support team investigates an unexpected price, address, or order.
Selection is a new authorization decision
An account ID submitted by the browser is a request, not proof of access. Load the current actor from trusted authentication state and resolve the target relationship through authoritative server-side data. Check that the account and any selected shopper are active and valid for the relevant store.
Avoid role-name shortcuts such as granting access because a display string contains 'manager'. Define the actual capability and relationship rules. A hierarchy traversal needs cycle detection, depth and result limits, and a clear response when its source is unavailable. Do not turn a failed permission read into an unrestricted account list.
Authorize the requested buying context
Call authorizeBuyingContext on the Next.js server when a signed-in user selects an account. Build Actor from server authentication, and treat requestedAccountId as an untrusted selection. CurrentGrantReader must read the current relationship between that user, account, and Store.
The helper requires an active user, an active account, purchasing permission, and matching user, account, and Store IDs. It returns a BuyingContext containing those three IDs. Map your installed account APIs and any project-specific grant rules into CurrentGrantReader; that interface is defined by this example.
accounts/authorize-buying-context.ts typescript
export type Actor = Readonly<{ userId: number; storeId: number; active: boolean }>;
export type AccountGrant = Readonly<{
actorId: number; accountId: number; storeId: number;
accountActive: boolean; canPurchase: boolean;
}>;
export type BuyingContext = Readonly<{
actorId: number; accountId: number; storeId: number;
}>;
export interface CurrentGrantReader {
find(actorId: number, accountId: number, storeId: number):
Promise<AccountGrant | null>;
}
export async function authorizeBuyingContext(
authenticatedActor: Actor, requestedAccountId: number,
grants: CurrentGrantReader
): Promise<BuyingContext> {
if (!authenticatedActor.active ||
!Number.isSafeInteger(authenticatedActor.userId) || authenticatedActor.userId < 1 ||
!Number.isSafeInteger(authenticatedActor.storeId) || authenticatedActor.storeId < 1 ||
!Number.isSafeInteger(requestedAccountId) || requestedAccountId < 1)
throw new Error("A valid active context is required.");
const grant = await grants.find(authenticatedActor.userId,
requestedAccountId, authenticatedActor.storeId);
if (!grant || !grant.accountActive || !grant.canPurchase ||
grant.actorId !== authenticatedActor.userId ||
grant.accountId !== requestedAccountId ||
grant.storeId !== authenticatedActor.storeId)
throw new Error("Account access denied.");
return Object.freeze({
actorId: authenticatedActor.userId,
accountId: requestedAccountId,
storeId: authenticatedActor.storeId
});
}
// Supply Actor from trusted server authentication, never request JSON.
// CurrentGrantReader maps current platform relationships into this policy.
If user 7 selects account 24 in Store 3, the reader must return a grant for user 7, account 24, and Store 3. A grant for account 25 is rejected even if it allows purchasing. The returned actorId remains 7, so orders and audit records still identify the person who signed in. Authorize protected operations again when they execute because relationships can change after selection.
Reload the values that belong to the account
After a switch, replace or reload every value that belongs to the old account: prices, cart, addresses, account documents, and cached responses. Block account-dependent actions while the new values load. An old request must not populate the screen after the selected account changes.
Carrying product selections across accounts is a separate business operation. If you support it, validate each selection against the new account's catalog, quantity rules, contract prices, and shipping restrictions before creating its cart.
Test relationship changes, not just happy switches
Use fictional accounts with visibly different catalogs or prices. Verify a permitted switch, an unauthorized target, an expired context, a removed relationship, and a locked actor. Try accessing a document directly after switching rather than relying on navigation to hide it.
Exercise hierarchy cycles and a permission-service outage. Verify that the actor remains unchanged in audit context while the selected account changes. Then run concurrent requests during a switch and ensure an old account response cannot populate the new account's screen. Retain these relationship-change cases in the account-context regression suite.
- Authorize account-scoped reads and writes independently.
- Keep account and store dimensions in relevant cache keys.
- Do not log raw session tickets or authentication material.
Make the distinction visible to buyers
A compact account indicator should name the active buying organization and provide a deliberate way to switch. If switching invalidates an in-progress cart, explain the transition before losing useful input. Clear context is part of preventing purchasing mistakes, not just a convenience feature.
Document who can select which accounts and why. That policy becomes the shared reference for backend authorization, storefront behavior, and support. When the actor remains stable and buying context is explicit, the system can support complex organizations without making identity depend on whichever account was clicked most recently.
References and further reading
Bring your next engineering question.
Design complex B2B account experiences
Independent guidance from GCG. Znode is a trademark of its owner. Examples use fictional data and are not official platform documentation. Suggest a correction.