How do I give my customers access to their data directly inside my application?
Alim Goulamhoussen
Publié le 21.06.24
Mis à jour le 21.09.26
10 min
Résumer cet article avec :
Giving your customers access to their own data inside your application sounds straightforward until you try to build it. The UI part (charts, tables, date filters) takes an afternoon to mock up. The access-control part (making sure each customer sees only their data and nothing else) takes architecture decisions that touch your database design, your authentication layer, your query pipeline, and your audit trail.
To give customers access to their data inside your product, you need six layers working together: authentication that resolves the user to a tenant, authorization for what they can do, data isolation enforced at the query layer (not the UI), governed metrics, embedded UX, and audit logging. The UI is the easy half. The isolation is the architecture, and it is where most implementations quietly break. This guide covers both, for CPOs and product teams shipping a customer data portal without a 12-month engineering detour.
TL;DR
Showing a customer their data is a UI problem. Ensuring they can only ever see their data is an authorization and isolation problem, and the two need different solutions. Resolve the tenant server-side, carry it in a signed token, and enforce it at query execution. Then embed the analytics so it feels native.
- Never derive tenant scope from a value the client controls. UI filters are not security.
- Enforce row-level security at the database and query layer, with a deny-by-default policy.
- Buying an embedded platform ships this in weeks; building it runs $371K–$630K over three years.
Giving customers access to their data inside your app, often called a customer-facing data portal or embedded analytics, means letting each customer explore their own data (filter, drill down, export) inside your product, while guaranteeing they can never see another customer's. It is two problems at once: a UI problem (showing the data) and a much harder authorization and data-isolation problem (making sure they only ever see their own). Solving the first while assuming the second is handled is how cross-tenant leaks happen. For the wider context, see what embedded analytics covers end to end.
What "customer data access inside your app" actually requires
The end state most teams want: a customer logs into your product, opens a reporting section, and explores data specific to their account. They can filter, drill down, export, and act on it. They cannot, under any circumstances, see another customer's data. That last requirement is where most implementations break down. The full stack you need to get right:
-
Authentication: who is the user, and which tenant do they belong to?
-
Authorization: what are they permitted to see and do (which dashboards, exports, metrics)?
-
Data isolation: when a query runs, does it return only rows belonging to that tenant?
-
Governed metrics: does "revenue" mean the same thing across dashboards, or can figures conflict depending on which chart loads first?
-
Embedded UX: does the analytics feel native to your product, or does it feel like a third-party tool was bolted on?
-
Auditability: can you reconstruct who accessed what, when, and from which dashboard, for compliance and incident response?
None of these layers is optional in a production portal. Skipping layer three and relying on UI filters is the most common failure mode.
The reference architecture: from app login to restricted data in-product
A production customer data portal involves four steps that must work together.
Step 1: App login and identity resolution. Your customer authenticates through your existing flow (password, SSO, OAuth). Your backend resolves their identity to a tenant identifier, for example tenant_id = "acme-corp". This identifier is authoritative. It never comes from a URL parameter or a client-side value the user can change.
Step 2: A scoped session token. When the user opens the analytics section, your server issues a short-lived, signed token carrying the tenant identifier and the user's permissions. The analytics layer trusts the token, not the browser. With Toucan, you sign a JWT server-side and exchange it for an opaque token; the tenant scope travels in the token's attributes, and only the opaque token reaches the frontend (see the embed authentication docs).
Step 3: Queries execute with a tenant predicate. Every query against your warehouse or database includes a tenant predicate derived from the token, not from a user-supplied filter. If the token says tenant_id = "acme-corp", every query appends WHERE tenant_id = 'acme-corp' at execution, in a layer the user cannot influence.
Step 4: Results rendered in-product. The query returns only that tenant's rows, and dashboards render them inside your interface. From the customer's side, this is just their analytics section. The isolation mechanics are invisible.
Customer login → backend resolves tenant_id
→ server issues signed token (tenant_id + permissions)
→ server exchanges it for an opaque embed token
→ analytics queries append the tenant predicate server-side
→ restricted result set returned
→ charts and dashboards rendered in-app
The key constraint: the tenant predicate is derived server-side and injected at query execution. Any architecture that derives it from a client request is bypassable.
Multi-tenancy isolation models: shared schema, schema-per-tenant, database-per-tenant
Before you write a line of security policy, decide how tenant data is stored. The model affects how you enforce isolation, how you scale, and how much operational overhead you accept.
Shared schema with row-level security. All tenants live in the same tables, with a tenant_id column marking ownership and RLS policies returning only matching rows. PostgreSQL enforces this with row-level security policies, and FORCE ROW LEVEL SECURITY makes them apply even to the table owner. Low operational cost; isolation strength depends on correct policy configuration and query-layer enforcement. Best for high tenant counts and a single deployment to manage.
Schema-per-tenant. Each tenant gets a dedicated schema in a shared database, with queries routed by tenant context and no RLS needed because schemas are structurally separate. Medium cost (migrations run across all schemas). Good for moderate tenant counts, per-tenant customization, or regulatory needs for schema separation.
Database-per-tenant. Each tenant has a dedicated database. Maximum isolation: data cannot leak across tenants even through a misconfigured query. High cost (connection management, migration orchestration, monitoring scale with tenant count). Best for enterprise customers with data-residency requirements or regulated industries.
| Factor | Shared + RLS | Schema-per-tenant | DB-per-tenant |
|---|---|---|---|
| Tenant count > 1,000 | Good fit | Caution | Impractical |
| Large per-tenant data volume | Works | Works | Best |
| Strict regulatory isolation | Risky | Possible | Best fit |
| Small team, one codebase | Best fit | Manageable | Avoid |
| Customer expects dedicated infra | No | Partial | Yes |
Why UI filtering fails: enforce authorization at the query layer
The most common shortcut: build a dashboard that filters by customer_id, pass the customer's ID from the session, and call it isolation. It is not. It is display-level filtering. If a user can change the filter value (through a URL parameter, the browser console, or an API call your frontend makes) they can retrieve another tenant's data. That class of bug is insecure direct object reference (IDOR), which falls under Broken Access Control, the number-one category in the OWASP Top 10.
The correct model separates two controls. RBAC (role-based access control) governs what a user can do: which dashboards they view, whether they can export, whether they can configure reports. RLS (row-level security) governs which rows a query returns: a user may have permission to view the Revenue dashboard, but RLS ensures they see only their tenant's revenue. Both are necessary. RBAC without RLS lets an authorized user reach other tenants' data if the query is not restricted; RLS without RBAC lets users land on dashboards they should never see at all.
Four pitfalls that cause cross-tenant leaks
- Bypass via direct API calls. If tenant scoping is applied only in your frontend, an authenticated user who calls the query API directly can omit it. Apply the predicate in the backend before the query reaches the database.
- Mismatched tenant predicates. If the token issues
tenant_idbut the query filters onorg_id, the predicate may not match. Map token claims to your data model explicitly and test both fields. - Caching that ignores tenant context. Result caches must be keyed on tenant, not just the query string, so one tenant's cached result is never served to another.
- Exports that bypass RLS. CSV and Excel jobs that run with elevated credentials return unscoped data. Every export must inherit the same tenant context as the dashboard query.
One database note worth planning for: connection poolers like PgBouncer can reuse a session without resetting tenant context, which silently defeats RLS. Reset the tenant context between requests, and put that case in your test plan.
Security checklist before you launch to production tenants
Token and session controls
-
Embed tokens are short-lived (15 minutes to 1 hour is typical), signed server-side, and validated on every request.
-
The token payload includes the tenant identifier and the user's permission set, and tokens cannot be reused after expiry or logout.
-
The refresh flow re-validates identity before re-issuing a token.
Data isolation at the query layer
-
RLS policies are enabled at the database level, default to deny, and derive the tenant predicate from the signed token, not a request parameter.
-
Connection-pool sessions reset tenant context between requests, and
FORCE ROW LEVEL SECURITYis set on all tenant-scoped tables.
Audit logging
-
Dashboard views and exports are logged with user ID, tenant ID, dashboard or dataset, row count, and timestamp.
-
Admin actions (permission and tenant-config changes) are logged, and logs are stored where the tenant cannot modify them.
Operational testing
-
Negative test: authenticate as Tenant A and attempt to reach Tenant B's data via direct API call, modified parameters, and cache-hit scenarios. Confirm 403 or empty result.
-
Export test and cache test: verify exports and cached views never surface another tenant's rows, and re-run the full suite whenever a permission changes.
If your app aggregates external data: OAuth, consent, and connection UX
If your application connects to external financial data sources, payment processors, or third-party platforms on behalf of your customers, you add an OAuth layer on top of the tenant-scoping architecture above. This is common in fintech, HR tech, and any SaaS aggregating data from multiple systems. OAuth does not replace tenant scoping: the consent grant produces a token you store against the tenant and use to fetch external data, which then flows into your data layer where your normal RLS and RBAC apply.
Customer clicks "connect" → OAuth authorization request
→ redirect to external provider (Stripe, Plaid, ...)
→ customer grants consent
→ provider redirects to your callback with an authorization code
→ your backend exchanges the code for access + refresh tokens
→ tokens stored server-side, associated with tenant_id
→ external data fetched, stored, and now tenant-scoped in queries
Two implementation notes that matter across providers. With Stripe Connect, the scope parameter (read_only or read_write) sets what your platform can do; register your redirect URI first, exchange the code server-side, and store the connected account ID against your tenant_id. With Plaid Link, initialize with a server-generated link_token, exchange the public_token immediately (it expires), store the access_token and item_id server-side, and subscribe to webhooks like ITEM_LOGIN_REQUIRED. In both cases, pass a signed, short-lived state parameter; the OAuth specification calls this out as CSRF protection.
| Failure mode | What it means | In-app UX response |
|---|---|---|
| User denies consent | Clicked "Cancel" on the consent screen | Non-alarming message: connection cancelled, connect anytime from Settings |
| Authorization code expired | Code not exchanged in its window (often 10 min) | Restart the flow silently; prompt after a second failure |
state mismatch |
Potential CSRF or session issue | Abort, log the event, retry from a fresh session |
item_login_required |
Credentials changed or MFA needed (Plaid) | Show a "Reconnect" prompt with a link to re-run Link |
| Token revoked | Customer revoked access at the provider | Remove the connection, stop fetches, prompt reconnection |
| Insufficient scope | Token lacks permissions your query needs | "Update permissions" prompt that re-runs OAuth with the right scope |
Treat connection state as a first-class entity: customers should see whether each connection is active, needs attention, or is disconnected without contacting support.
How embedded analytics turns your app into a customer data portal
Once the security architecture is in place, the remaining problem is the portal UX itself: dashboards, drill-downs, exports, and self-service reporting. Building this from scratch takes longer than most teams expect. The dashboard components alone (charting, date filters, drill-through, responsive layout) take weeks; add governed metrics, a semantic layer, and white-label branding and you are looking at a 6 to 12-month build and $371,000 to $630,000 over three years, per our build vs buy guide.
Toucan is built for this exact case: embedding analytics into customer-facing SaaS, where the end users are your customers rather than internal analysts. The capabilities map to the architecture above:
-
Governed semantic layer. A governed semantic layer keeps metric definitions consistent across every dashboard, so customers never see conflicting figures.
-
Row-level security. Tenant context is enforced at the query layer through the token's attributes, matching the requirement that isolation happens where queries run, not in the UI. See security and governance.
-
White-label, multi-tenant embedding. Analytics surfaces inside your app with your branding intact, and multi-tenant configuration is built in, so you are not building tenant routing from scratch.
-
No-code dashboard builder. Product and ops teams create, modify, and publish dashboards without pulling data engineering for every change, which removes the bottleneck that makes "ship analytics by Q3" slip.
-
Modern connectors and deployment. Connects to PostgreSQL, MySQL, Snowflake, BigQuery, Redshift, Databricks, and more, with SaaS or self-hosted deployment for regulated or data-residency needs.
For build vs buy, the question is not whether you can build a portal. You can. It is whether the analytics layer is the best use of engineering capacity that could go into your core product. For the deeper technical picture, see embedded analytics architecture and best practices.
A four-week implementation plan
This timeline assumes you use an embedded platform rather than building the analytics layer, with two to three engineers and a product manager.
| Week | Focus | Key deliverables |
|---|---|---|
| 1 | Discovery and data modeling | Metric inventory from tickets and calls; entity and tenant-key mapping; draft permission model |
| 2 | Security model and tenant scoping | RLS policies written and tested; signed-token issuance with tenant and permission claims; negative test suite |
| 3 | Embedding and dashboards | Analytics embedded in your nav; initial dashboards configured; connect flows if needed; white-label to your design |
| 4 | Hardening and launch | Audit logging live; permission-change regression tests; support runbook; performance baseline per tenant tier |
After week four you have a launchable product. Post-launch, dashboard content, self-service features, and export options iterate through the no-code builder without engineering on each change.
Ship the portal, not the plumbing
Giving customers access to their data inside your app comes down to one discipline: resolve the tenant server-side, carry it in a signed token, enforce it at query execution, and never trust the browser. Get that right and the UI is the easy part.
Toucan is an embedded AI analytics platform built for ISVs and SaaS product teams: white-label, customer-facing analytics with multi-tenant row-level security and conversational AI on a governed semantic layer, live in weeks rather than quarters. Start for free and ship your first customer-facing portal against your own data.
Frequently asked questions
How do I give my customers access to their data directly inside my application?
Resolve the customer's tenant from your verified login, issue a short-lived signed token that carries the tenant and permissions, and enforce that tenant scope at query execution rather than in the UI. Then embed the analytics so it feels native. The six layers you need are authentication, authorization, query-layer data isolation, governed metrics, embedded UX, and audit logging. Most teams buy an embedded platform to ship this in weeks instead of building it in months.
How do I prevent cross-tenant data leaks in embedded analytics?
Never derive tenant scope from a value the client controls. Derive it server-side from a verified identity, encode it in a signed token, and enforce it at query execution with row-level security. Run a negative test suite that authenticates as Tenant A and tries to reach Tenant B's data through direct API calls, modified parameters, and cache hits. If any test returns Tenant B's data, the isolation is incomplete.
Do I need row-level security if I only show per-customer dashboards?
Yes. "Per-customer dashboards" is a UI concept; the queries behind them still hit your database. If the database does not enforce tenant scoping at the query layer, a bug in your application (or a direct API call) can bypass the per-customer routing entirely. Database-level RLS is defense in depth: even with an application bug, the database will not return cross-tenant data.
Which multi-tenancy model should I use?
Shared schema with row-level security fits high tenant counts and a small team on one codebase. Schema-per-tenant suits moderate counts with per-tenant customization or schema-level separation needs. Database-per-tenant gives the strongest isolation for regulated industries or data-residency requirements, at the highest operational cost. Match the model to tenant count, data volume, and regulatory constraints before writing any security policy.
Can I let customers export their data safely?
Yes, if exports inherit the same tenant scoping as the dashboard queries that generated them. The common mistake is running export jobs with elevated or unscoped credentials. Every export must pass tenant context to the query layer and enforce row-level security exactly as a dashboard query does. Log each export with user ID, tenant ID, dataset, row count, and timestamp, so you have evidence if a customer later disputes what they exported.
Should I build this myself or use an embedded analytics platform?
You can build it, but the analytics layer is a product in itself: query engine, caching, multi-tenant RLS, a semantic layer, white-label theming, SSO, audit logging, and permanent maintenance. Building runs about $371,000 to $630,000 over three years and 6 to 12 months to a first dashboard, versus 4 to 8 weeks to integrate a platform. Build only when analytics is your core differentiator and you can own it long-term.
Alim Goulamhoussen
Alim is Head of Marketing at Toucan and a growth marketing expert with over 8 years of experience in the SaaS industry. Specialized in digital acquisition, conversion optimization, and scalable growth strategies, he helps businesses accelerate by combining data, content, and automation. On Toucan’s blog, Alim shares practical tips and proven strategies to help product, marketing, and sales teams turn data into actionable insights with embedded analytics. His goal: make data simple, accessible, and impactful to drive business performance.
Voir tous les articles