Introduction
For the past two years, I've been working on a large-scale multi-tenant financial platform serving 250+ financial institutions across the U.S. The system supports millions of end users, with around 50-60k monthly active users interacting across different portals.
Recently, we implemented a secure cross-application SSO flow between our admin operations portal and a dedicated reporting dashboard. What initially looked like a simple "View Dashboard" button turned into a deeper architectural challenge involving tenant isolation, token exchange, and strict access boundaries — where errors are costly, user trust is critical, and sensitive financial data is always at stake.
Before diving into SSO, it's important to understand how the platform itself is structured.
High-Level Architecture: Three Portals, Three Responsibilities
Our system isn't a single monolithic app. It consists of three independent portals:
- Customer Portal - used by end users interacting with financial products.
- Admin & Operations Portal - used internally to manage multiple tenants (financial institutions).
- Reporting Dashboard - focused on KPIs, analytics, and performance reporting.
Each portal has its own purpose, deployment lifecycle, and trust boundary. Separation allowed our team (compact 7-engineer group owning everything from architecture and design to development, testing, and deployment) to scale independently and reduced the risk of exposing sensitive reporting functionality directly inside operational workflows.
From a technology standpoint, we used React for the frontend and .NET for the backend.
From an identity perspective, this also meant authentication couldn't be tightly coupled to one application - especially in a multi-tenant setup.
Three Types of Users
Our architecture revolves around three main user roles:
- Customers - interact only with the customer portal.
- Financial Institution (FI) Employees - log directly into the reporting dashboard using username/password.
- Admins - operate from the admin portal and also require access into reporting for operation purposes.
The reporting dashboard was originally designed only for FI employees and single-tenant access patterns. Later, due to operational requirements, we needed to extend it to support both Admins and FI employees.

New Requirement
Admins have a simple "Reporting" button. On clicking it, they should be redirected to the Reporting Portal in a new tab without logging in again.
Admins already have higher privileges and multi-tenant roles, which complicates seamless access while maintaining strict isolation between tenants.
The Core Problem introduced with requirement
At first, this sounded like a simple redirect problem. In reality, it introduced deeper architectural questions around identity boundaries and tenant safety.
Admins operate the Admin & Operations Portal and can manage multiple financial institutions (FIs). When performing actions, they select a tenant and operate under that FI context.
With the new requirement:
- Admins expect instant access to the tenant's reporting environment.
- Tenant isolation must be preserved: the Reporting Portal should only allow access to the currently selected FI.
- We currently use JWT tokens in both portals to store access and role information.
Challenges:
- Maintain strict tenant isolation - an admin should not access data from another FI just because they have global privileges.
- Avoid sharing sessions directly between apps - no session cookies or tokens should be blindly reused.
- Prevent long-lived tokens from leaking across domains, which could compromise data security.
Problem with reusing Admin JWT:
Using the Admin JWT in the Reporting Portal would blur tenant boundaries. It risks cross-tenant data leaks, defeating the purpose of isolated reporting per FI.
SSO Solution: How We Enabled Secure Cross-App Access
Once we clearly understood the risks around tenant isolation and cross-application authentication, the goal became simple to define but harder to execute: enable admins to move from the operations portal to the reporting dashboard seamlessly - without sharing sessions directly or weakening security boundaries. It was about connecting two independent applications without breaking tenant isolation or expanding trust boundaries.
Overall Approach
Instead of sharing sessions or forwarding existing admin tokens, we implemented a token exchange based SSO flow. The admin portal initiates the process, but the reporting application ultimately issues and owns its own authentication token. This allowed each portal to remain autonomous while still providing a unified experience to the user.
The core principle behind the design was simple:
Never transfer long-lived identity between applications - exchange it for a new, purpose-built token.
Admin Portal Login Flow (Before SSO)
Before diving into the SSO flow, let's first understand how the Admin Portal login works in our system.
Step 1: Admin Authentication
Admins log in using a username and password. Behind the scenes, our custom Identity Server handles authentication. When credentials are submitted:
- The Identity Server validates the admin.
- If authentication succeeds, it issues a JWT.
This initial token contains:
- Admin metadata
- A list of accessible banks
- High-level permissions and claims
At this point, the token is not scoped to a specific bank.
Step 2: Bank Selection
After login, the admin is presented with a list of banks they are authorized to manage.
When the admin selects a bank:
The frontend sends a request to the Identity Server with the selected bankGuid.
The Identity Server verifies whether the admin has permission to access that specific bank.
If authorized, a new JWT is generated - this time scoped to the selected bank. The new token includes bank-level permissions and context. The token is returned to the admin.
The frontend then replaces the initial token with this bank-scoped token and uses it for all subsequent API requests.
Why We Use bankGuid Instead of Primary Keys
We never expose actual database primary keys to the UI.
Instead, we use temporary, globally unique identifiers (bankGuid) that map internally to bankId.
Key characteristics of this mapping:
- Automatically generated
- Globally unique
- Refreshed approximately every 30 minutes
- New GUIDs are reassigned after refresh
This provides:
- An abstraction layer between UI and database
- Protection against direct ID enumeration
- Reduced risk of exposing internal data structures
Token Expiry and Seamless Refresh
Bank-scoped JWTs expire after ~20 minutes. To maintain uninterrupted access:
- The frontend uses a refresh token to request a new access token when the current one expires.
- If the bank’s bankGuid has rotated in the meantime, the new token contains the current valid GUID.
- Any request made with an old token automatically triggers this refresh flow. The new token replaces the old one transparently.
This ensures:
- No broken sessions
- No manual re-login required
- Continuous, secure access under the correct bank context
The
bankGuidrotation is designed to minimize exposure of stable internal IDs, and does not invalidate active sessions. During token refresh, the current valid bankGuid is always issued to ensure seamless continuity.
Since each JWT is scoped to a single bank:
The admin operates strictly within one bank context at a time. Opening multiple tabs does not allow simultaneous operation across different banks under the same session. In financial systems, "context confusion" is a real risk. An admin believing they are working in Bank A while modifying Bank B is not a theoretical problem - it’s an operational nightmare.
By enforcing bank-scoped tokens, we eliminate context confusion and significantly reduce the risk of unintended changes across financial institutions.

With bank-scoped tokens firmly established in the Admin Portal, the next challenge was securely extending this tenant context to the Reporting Dashboard without compromising isolation or security boundaries.
The Token Exchange Flow: Step-by-Step
Once the admin is authenticated and the bank-scoped JWT is issued, the next task is to securely extend the admin's session to the Reporting Portal while maintaining tenant isolation. Instead of transferring the long-lived bank-scoped JWT between applications, we implemented a token exchange flow to ensure security boundaries are respected.
Here's a detailed breakdown of how it works:
Step 1: Admin Requests Access to Reporting
- The admin clicks the "Reporting" button on the dashboard.
- The admin frontend sends a Single Sign-On (SSO) token request to the admin backend, including the bank context extracted from the JWT stored in local storage.
- The admin backend forwards the token to the identity server.
- The identity server validates the token and checks the admin’s permissions for accessing the reporting system.
- If valid, the identity server creates a short-lived, encrypted token (2-minute expiry) containing the admin info and bank context, then returns it to the admin backend.
- The admin backend forwards this short-lived SSO token to the admin frontend.
Step 2: Redirect to Reporting Dashboard
- The admin frontend opens the Reporting Dashboard in a new browser tab, sending the short-lived SSO token along with the request.
- The request reaches the reporting frontend, which passes it to the reporting backend.
- The reporting backend sends the SSO token to the identity server for verification.
- Upon validation, the identity server issues a new JWT containing the admin’s reporting permissions and bank context.
- The reporting frontend uses this new JWT for all subsequent requests, ensuring secure access without ever exposing the long-lived bank-scoped JWT.
Why This Approach Works
Tenant Isolation: The admin's bank context is always scoped to the reporting system.
Short-lived Tokens: Minimize exposure of sensitive credentials during redirects.
Centralized Validation: The identity server enforces permissions at every stage, ensuring only authorized access.
Edge Case: Context Confusion Across Multiple Tabs
You might think the token exchange flow is bulletproof and technically, it is. Strict tenant isolation, short-lived tokens, and carefully scoped permissions make cross-bank data leaks practically impossible. But here's the twist: sometimes, it’s the human — not the code — who becomes the weak link.
Picture this:
- An admin logs into the Admin Portal and selects Bank A.
- He/She click the Reporting button, opening Bank A's Reporting Dashboard in a new tab - all good.
- Then, the admin goes back to the Admin Portal, navigates to the home page, and switches to Bank B.
- He/She click Reporting again, opening Bank B's Reporting Dashboard in a second tab.
At this point:
- The first tab still shows Bank A's dashboard.
- The second tab shows Bank B's dashboard.
The system is doing exactly what it should: both dashboards enforce correct bank-level access, and the backend has kept each tenant neatly separated.
However, the user could get confused, thinking that both tabs are linked to the same bank context or confuse between banks. This may lead to operational mistakes, like performing actions in Bank B while thinking they are in Bank A.
Even with strict backend isolation, UX design must reinforce tenant boundaries to prevent
"context confusion"that could lead to mistakes.
Mitigation: Real-Time Tab Coordination
Even with single-session rules, admins may already have a reporting tab open when they try to open another. To handle this smoothly and prevent confusion, we implemented cross-tab messaging:Broadcasting Events Between Tabs
We use the browser's Broadcast Channel API to send messages to all open tabs of the same application.
- When a new reporting session starts, the reporting frontend broadcasts a "reporting session started" message to all other tabs.
- Upon receiving the broadcast, any previous reporting tab automatically logs out the active reporting session.
This ensures only one reporting session per browser at any time.
Tradeoffs
Implementing a secure, token-exchange SSO with strict tenant isolation isn't free - it comes with tradeoffs that are worth acknowledging.
- Performance :
- Each SSO flow involves extra redirects and token validation steps.
- We accepted this tradeoff because correctness, security, and tenant isolation outweigh shaving milliseconds.
- Over-Trusting Tokens
- Tokens must never be blindly trusted across applications or sessions.
- Sharing tokens between portals would break tenant boundaries and risk sensitive data exposure.
- The token-exchange flow mitigates this, but it requires careful validation, short lifetimes, and consistent backend enforcement.
- User Experience vs Security
- Enforcing one reporting session per device prevents context confusion but may frustrate admins who are used to opening multiple tabs, but it's a UX compromise to maintain security.
- Implementation Complexity
- Adding token exchange, session invalidation, and cross-tab messaging adds more moving parts.
- Each component must be carefully tested to avoid race conditions, token mismatches, or failed SSO flows.
- For a small team, this increases maintenance overhead, but it's necessary for enterprise-grade multi-tenant security.
Lesson Learned :
Even with rock-solid backend isolation, UX design acts as the guardian.
From this experience, we realized:
- Technical safeguards aren't enough: No matter how airtight your token exchange or permission model is, users can still get confused.
- Cross-tab coordination is essential: Real-time messaging between tabs prevents
"ghost sessions"and reduces operational mistakes.
Conclusion
Designing secure cross-application SSO in a multi-tenant financial system pushed us to think far beyond simple redirects and token handling. It required clear trust boundaries, purpose-built token exchange instead of shared identity, strict tenant scoping, controlled session invalidation, and thoughtful UX decisions to prevent context confusion. In environments where sensitive financial data is involved, even small architectural shortcuts can lead to serious consequences, so every layer (identity server, token lifecycle, tenant mapping, and frontend behavior) must work together intentionally. SSO is not just about seamless access; it is about enforcing boundaries while preserving usability. When built correctly, it becomes a disciplined bridge between independent systems rather than a shortcut around authentication.
Keep learning, keep growing.

Top comments (0)