Multi-tenant mobile applications look simple from the login screen: enter a company code, username, and password.
Behind that screen, the architecture is different from a normal single-backend app.
The application may need to discover a tenant, switch API base URLs, persist company configuration, authenticate against tenant-specific infrastructure, refresh tokens safely, isolate cached data, and recover cleanly when a session expires.
Tenant discovery comes before authentication
A common pattern is:
company code -> tenant lookup -> base URL -> login -> access token
This means the base URL itself becomes part of session context.
I do not want random features constructing their own endpoints or reading the merchant code directly.
The resolved tenant belongs in a dedicated configuration layer that exposes the active API environment to the networking stack.
Company state and user state are different
One subtle design decision is deciding what “logout” should clear.
For many enterprise apps:
- Access token should be cleared.
- Refresh token should be cleared.
- User profile and user-scoped caches should be cleared.
- Push notification identity should be cleared.
But the selected company may remain.
Why?
Because “logout user” and “change company” are different actions.
If the tenant is cleared every time, logging out can unexpectedly send the user back to tenant discovery instead of the normal login screen.
Making these lifecycle boundaries explicit prevents confusing behavior.
Refresh should be centralized
Every feature should not implement its own 401 logic.
I prefer one networking layer that can:
- Detect an expired access token.
- Serialize refresh attempts.
- Refresh once.
- Store the new credentials.
- Retry eligible requests.
- Fail the session safely if refresh is rejected.
The serialization part matters.
If five requests receive 401 at the same time, you do not want five refresh requests racing and overwriting each other's tokens.
Avoid infinite interceptor loops
The refresh endpoint itself must not trigger the same refresh interceptor.
Otherwise a failed refresh can recursively attempt to refresh forever.
The network layer needs clear exclusions and a terminal authentication failure state.
At that point the application should clear the local session and reset navigation.
Tenant isolation includes local storage
Switching the API base URL is not enough.
If local databases or caches are shared, one tenant may see stale data from another tenant.
Depending on the application, I namespace local data by tenant ID or fully clear tenant-scoped storage when the company changes.
I also check:
- Offline queues.
- Cached catalogs.
- Saved documents.
- User preferences.
- Pending uploads.
- Notification identities.
Anything scoped to one company must not silently survive into another.
Navigation must react to session state
Authentication should be state, not a collection of manual navigation calls.
When the session becomes invalid, the router should converge on the login state and prevent back-navigation into authenticated screens.
This is especially important after logout, refresh failure, or tenant switching.
The architecture test
A multi-tenant system is healthy when I can answer these questions clearly:
- What identifies the current tenant?
- Where is the base URL stored?
- Which data survives logout?
- Which data survives company change?
- Who owns token refresh?
- What happens when refresh fails?
- How are concurrent 401 responses handled?
- Can tenant A ever see cached data from tenant B?
If those answers are scattered across screens, the architecture needs work.
Multi-tenancy is not a login feature. It is a cross-cutting system boundary.