> ## Documentation Index
> Fetch the complete documentation index at: https://docs.papermap.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Iframe Embed Tokens

This guide covers how to generate secure, time-limited tokens for embedding dashboards in your application.

<Info>
  This page is specifically about the multi-tenant iframe token flow. If you are
  embedding Papermap through `@papermap/papermap`, see [React
  Components](/documentation/embedding/react-components) for the component
  integration flow and how it uses `workspaceId`, `dashboardId`, and backend
  token generation.
</Info>

## 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

<Warning>
  This token generation is different from the one in the [Basic
  Embedding](/documentation/embedding/basic-embedding#backend%3A-generating-embed-tokens) section. The token
  generation in this section is for multi-tenant iframe embedding.
</Warning>

<Info>
  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.
</Info>

<CodeGroup>
  ```python Python theme={null}
  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
      }
  ```

  ```typescript TypeScript/Node.js theme={null}
  function generateIframeToken(
    tenantId: string,
    workspaceId: string,
    dashboardId: string,
    validUntil?: number
  ): string {
    const expiresAt = validUntil || Math.floor(Date.now() / 1000) + 3600;
    const validUntilStr = String(expiresAt);

    const signature = this.createSignature(workspaceId, validUntilStr);

    const tokenData = {
      api_key_id: this.config.apiKeyId,
      workspace_id: workspaceId,
      tenant_id: tenantId,
      dashboard_id: dashboardId,
      valid_until: expiresAt,
      signature,
    };

    return Buffer.from(JSON.stringify(tokenData)).toString("base64url");
  }

  // Express endpoint
  app.post("/api/dashboards/iframe-token", authenticateUser, async (req, res) => {
    const { tenantId, workspaceId, dashboardId } = req.body;

    // Verify tenant belongs to user
    if (req.user.tenantId !== tenantId) {
      return res.status(403).json({ error: "Access denied" });
    }

    const token = generateIframeToken(tenantId, workspaceId, dashboardId);
    res.json({ token, dashboard_id: dashboardId });
  });
  ```

  ```php PHP theme={null}
  <?php
  function generateIframeToken($tenantId, $workspaceId, $dashboardId, $validUntil = null) {
      $expiresAt = $validUntil ?? time() + 3600; // 1 hour

      $signature = createSignature(
          $workspaceId,
          (string)$expiresAt,
          getenv('PAPERMAP_SECRET_KEY')
      );

      $tokenData = [
          'api_key_id' => getenv('PAPERMAP_API_KEY_ID'),
          'workspace_id' => $workspaceId,
          'tenant_id' => $tenantId,
          'dashboard_id' => $dashboardId,
          'valid_until' => $expiresAt,
          'signature' => $signature
      ];

      $token = base64_encode(json_encode($tokenData));
      $token = str_replace(['+', '/', '='], ['-', '_', ''], $token);

      return [
          'token' => $token,
          'dashboard_id' => $dashboardId,
          'expires_at' => $expiresAt
      ];
  }
  ?>
  ```

  ```java Java theme={null}
  public String generateIframeToken(
      String tenantId,
      String workspaceId,
      String dashboardId,
      Long validUntil
  ) throws Exception {
      if (validUntil == null) {
          validUntil = Instant.now().getEpochSecond() + 3600;
      }

      String signature = createSignature(workspaceId, String.valueOf(validUntil));

      Map<String, Object> tokenData = new HashMap<>();
      tokenData.put("api_key_id", apiKeyId);
      tokenData.put("workspace_id", workspaceId);
      tokenData.put("tenant_id", tenantId);
      tokenData.put("dashboard_id", dashboardId);
      tokenData.put("valid_until", validUntil);
      tokenData.put("signature", signature);

      String json = objectMapper.writeValueAsString(tokenData);
      return Base64.getUrlEncoder()
          .withoutPadding()
          .encodeToString(json.getBytes(StandardCharsets.UTF_8));
  }
  ```

  ```csharp C# theme={null}
  public string GenerateIframeToken(
      string tenantId,
      string workspaceId,
      string dashboardId,
      long? validUntil = null
  )
  {
      var expiresAt = validUntil ?? DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 3600;
      var signature = CreateSignature(workspaceId, expiresAt.ToString());

      var tokenData = new
      {
          api_key_id = _apiKeyId,
          workspace_id = workspaceId,
          tenant_id = tenantId,
          dashboard_id = dashboardId,
          valid_until = expiresAt,
          signature
      };

      var json = JsonSerializer.Serialize(tokenData);
      var bytes = Encoding.UTF8.GetBytes(json);

      return Convert.ToBase64String(bytes)
          .Replace('+', '-')
          .Replace('/', '_')
          .TrimEnd('=');
  }
  ```

  ```go Go theme={null}
  func (s *DashboardAPIService) GenerateIframeToken(
      tenantID, workspaceID, dashboardID string,
      validUntil *int64,
  ) (string, error) {
      var expiresAt int64
      if validUntil == nil {
          expiresAt = time.Now().Unix() + 3600
      } else {
          expiresAt = *validUntil
      }

      signature := s.createSignature(
          workspaceID,
          fmt.Sprintf("%d", expiresAt),
      )

      tokenData := map[string]interface{}{
          "api_key_id":   s.APIKeyID,
          "workspace_id": workspaceID,
          "tenant_id":    tenantID,
          "dashboard_id": dashboardID,
          "valid_until":  expiresAt,
          "signature":    signature,
      }

      jsonData, _ := json.Marshal(tokenData)
      encoded := base64.URLEncoding.EncodeToString(jsonData)

      return strings.TrimRight(encoded, "="), nil
  }
  ```
</CodeGroup>

## Using the Token in Frontend

Once you have the token, embed the dashboard using an iframe:

```html theme={null}
<iframe
  src="https://papermap.ai/embedded/dashboard?embedded-token={YOUR_TOKEN}"
  width="100%"
  height="600px"
  frameborder="0"
></iframe>
```

### React Example

```tsx theme={null}
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:

```typescript theme={null}
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

<Warning>
  **Always verify tenant ownership before generating tokens**

  Never generate a token without verifying that the requesting user has access to the tenant's dashboard.
</Warning>

### Tenant Access Verification

<CodeGroup>
  ```python Python theme={null}
  # 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)
  ```

  ```typescript TypeScript theme={null}
  // Good - Verify tenant access
  if (req.user.tenantId !== tenantId) {
    return res.status(403).json({ error: "Access denied" });
  }

  // Then generate token
  const token = generateIframeToken(tenantId, workspaceId, dashboardId);
  ```

  ```php PHP theme={null}
  // Good - Verify tenant access
  if (Auth::user()->tenant_id !== $tenantId) {
      return response()->json(['error' => 'Access denied'], 403);
  }

  // Then generate token
  $token = generateIframeToken($tenantId, $workspaceId, $dashboardId);
  ```
</CodeGroup>

### 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

1. **Generate tokens on-demand**: Don't pre-generate and store tokens
2. **Use HTTPS**: Always serve your application over HTTPS
3. **Implement token refresh**: Refresh tokens before they expire
4. **Log token generation**: Monitor for unusual access patterns
5. **Rate limit**: Prevent abuse by rate limiting token generation

## Next Steps

<CardGroup cols={2}>
  <Card title="API Endpoints" icon="plug" href="/documentation/multi-tenancy/api-endpoints">
    Set up REST API endpoints for dashboard operations
  </Card>

  <Card title="Security Best Practices" icon="shield-check" href="/documentation/multi-tenancy/security">
    Learn about security considerations for production
  </Card>
</CardGroup>
