This guide covers how to generate secure, time-limited tokens for embedding dashboards in your application.
This page is specifically about the multi-tenant iframe token flow. If you are
embedding Papermap through @papermap/papermap, see React
Components for the component
integration flow and how it uses workspaceId, dashboardId, and backend
token generation.
Overview
Iframe tokens allow you to securely embed Papermap dashboards in your application. Each token:
Is signed with HMAC-SHA256 for security
Has a configurable expiration time
Is specific to a tenant and dashboard
Can only be used for the specified dashboard
Token Structure
The token contains:
api_key_id: Your API key identifier
workspace_id: Your workspace ID
tenant_id: The tenant identifier
dashboard_id: The specific dashboard ID
valid_until: Unix timestamp when token expires
signature: HMAC signature for verification
Generating Iframe Tokens
This token generation is different from the one in the Basic
Embedding section. The token
generation in this section is for multi-tenant iframe embedding.
The tenant-aware dashboardId and backend provisioning model described here
can still be relevant when your application uses React components, but this
page documents the iframe-specific token flow.
Python
TypeScript/Node.js
PHP
Java
C#
Go
import json
import base64
import time
def generate_iframe_token (
tenant_id : str ,
workspace_id : str ,
dashboard_id : str ,
valid_until : Optional[ int ] = None
):
# 1. Get tenant's dashboard
tenant_dashboard = TenantDashboard.read(
session = db,
filter_by = { "tenant_id" : tenant_id}
)
if not tenant_dashboard:
raise Exception ( "Dashboard not found for tenant" )
# 2. Set expiration (default 1 hour)
if valid_until is None :
valid_until = int (time.time()) + 3600
# 3. Create HMAC signature
payload = f " { workspace_id }{ valid_until } "
signature = hmac.new(
SECRET_KEY .encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
# 4. Encode token
token_data = {
"api_key_id" : API_KEY_ID ,
"workspace_id" : workspace_id,
"tenant_id" : tenant_id,
"dashboard_id" : dashboard_id,
"valid_until" : valid_until,
"signature" : signature
}
token_json = json.dumps(token_data)
encoded_token = base64.urlsafe_b64encode(token_json.encode()).decode()
return {
"token" : encoded_token,
"dashboard_id" : dashboard_id,
"expires_at" : valid_until
}
Using the Token in Frontend
Once you have the token, embed the dashboard using an iframe:
< iframe
src = "https://papermap.ai/embedded/dashboard?embedded-token={YOUR_TOKEN}"
width = "100%"
height = "600px"
frameborder = "0"
></ iframe >
React Example
import React , { useState , useEffect } from "react" ;
function DashboardEmbed ({ tenantId , workspaceId , dashboardId }) {
const [ token , setToken ] = useState ( null );
const [ loading , setLoading ] = useState ( true );
useEffect (() => {
async function fetchToken () {
const response = await fetch ( "/api/dashboards/iframe-token" , {
method: "POST" ,
headers: { "Content-Type" : "application/json" },
body: JSON . stringify ({ tenantId , workspaceId , dashboardId }),
});
const data = await response . json ();
setToken ( data . token );
setLoading ( false );
}
fetchToken ();
}, [ tenantId , workspaceId , dashboardId ]);
if ( loading ) return < div > Loading dashboard... </ div > ;
return (
< iframe
src = { `https://papermap.ai/embedded/dashboard?embedded-token= ${ token } ` }
width = "100%"
height = "600px"
frameBorder = "0"
title = "Dashboard"
/>
);
}
Workflow Diagram
1. Client → Request Embed Token
POST /api/v1/dashboards/iframe-token
{
"tenant_id": "org-123",
"workspace_id": "workspace-456"
}
2. Your API → Generate Token
- Lookup dashboard_id for tenant
- Create HMAC signature
- Encode as base64 token
3. Return Token to Client
{
"token": "eyJhcGlfa2V5X2lkIjoi...",
"dashboard_id": "dashboard-789",
"expires_at": 1700000000
}
4. Frontend → Embed in Iframe
<iframe src="https://papermap.ai/embedded/dashboard?embedded-token=eyJhcGlfa2V5X2lkIjoi..." />
Token Refresh
Since tokens expire, implement a refresh mechanism:
class DashboardTokenManager {
private token : string | null = null ;
private expiresAt : number | null = null ;
async getToken ( tenantId : string , workspaceId : string , dashboardId : string ) {
// Check if token is still valid (with 5 min buffer)
if (
this . token &&
this . expiresAt &&
this . expiresAt > Date . now () / 1000 + 300
) {
return this . token ;
}
// Fetch new token
const response = await fetch ( "/api/dashboards/iframe-token" , {
method: "POST" ,
headers: { "Content-Type" : "application/json" },
body: JSON . stringify ({ tenantId , workspaceId , dashboardId }),
});
const data = await response . json ();
this . token = data . token ;
this . expiresAt = data . expires_at ;
return this . token ;
}
}
Security Considerations
Always verify tenant ownership before generating tokens Never generate a token without verifying that the requesting user has access to the tenant’s dashboard.
Tenant Access Verification
# Good - Verify tenant access
if current_user[ "organization_id" ] != tenant_id:
raise HTTPException( status_code = 403 , detail = "Access denied" )
# Then generate token
token = generate_iframe_token(tenant_id, workspace_id, dashboard_id)
Token Expiration
Advisable: 1 hour (3600 seconds)
Configurable based on your security requirements
Shorter expiration = more secure, but requires more frequent refreshes
Longer expiration = better UX, but higher security risk
Best Practices
Generate tokens on-demand : Don’t pre-generate and store tokens
Use HTTPS : Always serve your application over HTTPS
Implement token refresh : Refresh tokens before they expire
Log token generation : Monitor for unusual access patterns
Rate limit : Prevent abuse by rate limiting token generation
Next Steps
API Endpoints Set up REST API endpoints for dashboard operations
Security Best Practices Learn about security considerations for production