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

# Security Best Practices

This guide covers security considerations and best practices for implementing multi-tenant dashboards in production.

## HMAC Signature Verification

### How Verification Works

The Papermap API verifies requests by:

1. Extracting `workspace_id` and `valid_until` from headers
2. Computing signature: `HMAC-SHA256(workspace_id + valid_until, secret_key)`
3. Comparing computed signature with provided `X-Signature`
4. Checking `valid_until` hasn't expired

<Warning>
  If the signature doesn't match or the timestamp has expired, the request is
  rejected with a 401 Unauthorized error.
</Warning>

### Signature Pattern

```
1. Create Payload
   payload = workspace_id + valid_until

2. Generate Signature
   signature = HMAC-SHA256(payload, secret_key)

3. Send Request with Headers
   X-API-Key-ID: <api_key>
   X-Workspace-ID: <workspace_id>
   X-Valid-Until: <timestamp>
   X-Signature: <signature>

4. Papermap Verifies
   - Recomputes signature with same payload
   - Compares with provided signature
   - Checks timestamp hasn't expired
```

## Token Expiration

### Papermap API Requests

* **Duration**: 5 minutes vallidity advisable
* **Configuration**: `valid_until = now + 300`
* **Purpose**: Short-lived to prevent replay attacks

### Iframe Tokens

* **Duration**: 1 hour validity (configurable)
* **Configuration**: `valid_until = now + 3600`
* **Purpose**: Long enough for user sessions, short enough for security

### Expired Token Handling

Expired tokens are automatically rejected by Papermap API:

```json theme={null}
{
  "error": "Token expired",
  "code": "TOKEN_EXPIRED",
  "status": 401
}
```

Implement automatic token refresh in your frontend to handle expiration gracefully.

## Secret Key Management

<Warning>
  **Never commit secrets to version control!**

  Use environment variables or a secrets management service like:

  * AWS Secrets Manager
  * HashiCorp Vault
  * Azure Key Vault
  * Google Cloud Secret Manager
</Warning>

### Environment Variables

Store credentials securely in environment variables:

```bash .env theme={null}
PAPERMAP_API_KEY_ID=your-api-key-id
PAPERMAP_SECRET_KEY=your-secret-key-never-share
PAPERMAP_API_URL=<your-api-endpoint>
```

<Info>
  **Obtaining Your Configuration:** - **API Credentials**: Available in your
  Papermap dashboard under **Settings → API Keys** - **API Endpoint**: Available
  in **Settings → API Configuration** - Always use the values provided in your
  dashboard for your specific workspace
</Info>

### Key Rotation

Regularly rotate your API keys:

1. Generate new API key in Papermap dashboard
2. Update environment variables in your application
3. Deploy updated configuration
4. Revoke old API key after confirming new one works

**Recommended rotation schedule**: Every 90 days or immediately if compromised

## Tenant Isolation

### Always Verify Tenant Access

Never generate tokens or retrieve dashboards without verifying the user has access to the tenant:

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

  # ❌ Bad - No verification
  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);

  // ❌ Bad - No verification
  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);

  // ❌ Bad - No verification
  $token = generateIframeToken($tenantId, $workspaceId, $dashboardId);
  ```
</CodeGroup>

### Database-Level Isolation

Ensure your database queries respect tenant boundaries:

```python theme={null}
# ✅ Good - Filter by tenant
dashboard = db.query(TenantDashboard).filter_by(
    tenant_id=tenant_id,
    organization_id=current_user.organization_id
).first()

# ❌ Bad - No tenant filter
dashboard = db.query(TenantDashboard).filter_by(
    dashboard_id=dashboard_id
).first()
```

## HTTPS and Transport Security

<Warning>
  **Always use HTTPS in production**

  HTTP traffic can be intercepted, exposing your API keys and tokens.
</Warning>

### Force HTTPS

<CodeGroup>
  ```python Python (FastAPI) theme={null}
  from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware

  app.add_middleware(HTTPSRedirectMiddleware)
  ```

  ```typescript TypeScript (Express) theme={null}
  function forceHTTPS(req, res, next) {
    if (req.headers["x-forwarded-proto"] !== "https") {
      return res.redirect(301, `https://${req.hostname}${req.url}`);
    }
    next();
  }

  app.use(forceHTTPS);
  ```
</CodeGroup>

### CORS Configuration

Configure CORS properly to prevent unauthorized domains from accessing your API:

<CodeGroup>
  ```python Python (FastAPI) theme={null}
  from fastapi.middleware.cors import CORSMiddleware

  app.add_middleware(
      CORSMiddleware,
      allow_origins=["https://yourdomain.com"],  # Specific domains only
      allow_credentials=True,
      allow_methods=["GET", "POST"],
      allow_headers=["Authorization", "Content-Type"],
  )
  ```

  ```typescript TypeScript (Express) theme={null}
  import cors from "cors";

  app.use(
    cors({
      origin: "https://yourdomain.com", // Specific domain only
      credentials: true,
      methods: ["GET", "POST"],
      allowedHeaders: ["Authorization", "Content-Type"],
    })
  );
  ```
</CodeGroup>

## Monitoring Alerts

Set up alerts for suspicious activity:

* Multiple failed authentication attempts
* Unusual number of token generation requests
* Access attempts from unexpected IP addresses
* Expired token usage attempts

## Security Checklist

Use this checklist before going to production:
<Check>API keys stored in environment variables (not in code)</Check>
<Check>HTTPS enforced on all endpoints</Check>
<Check>Tenant access verification on all endpoints</Check>
<Check>JWT tokens have proper expiration</Check>
<Check>Monitoring and alerts configured</Check>
<Check>Key rotation schedule established</Check>
<Check>Error messages don't expose sensitive info</Check>
<Check>API endpoint URL treated as sensitive</Check>

## Common Vulnerability to Avoid

### 1. Signature Replay Attacks

The `valid_until` timestamp prevents replay attacks. Never:

* Reuse old signatures
* Set very long expiration times for API requests
* Remove timestamp validation

## Next Steps

<CardGroup cols={2}>
  <Card title="Best Practices" icon="star" href="/documentation/multi-tenancy/best-practices">
    Learn about performance optimization and scalability
  </Card>

  <Card title="Authentication Guide" icon="key" href="/documentation/multi-tenancy/authentication">
    Deep dive into HMAC authentication
  </Card>
</CardGroup>
