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

# HMAC Authentication

This guide covers the HMAC signature authentication pattern used to secure requests to the Papermap Dashboard API.

## How HMAC Authentication Works

The core security pattern uses HMAC-SHA256 to sign requests to the Papermap API:

1. Concatenate `workspace_id` + `valid_until` timestamp
2. Create HMAC-SHA256 hash using your secret key
3. Send signature + timestamp in headers
4. Papermap API verifies signature with the same secret

### Authentication Flow

```
Your API → Generate HMAC Signature
   payload = "workspace-456" + "1699999999"
   signature = HMAC-SHA256(payload, secret_key)

Your API → Call Papermap Dashboard API
   POST ${PAPERMAP_API_URL}/api/v1/external/dashboards
   Headers:
     X-API-Key-ID: your-api-key
     X-Workspace-ID: workspace-456
     X-Valid-Until: 1699999999
     X-Signature: abc123...

Papermap API → Verifies Signature
   - Extracts workspace_id and valid_until
   - Computes: HMAC-SHA256(workspace_id + valid_until, secret_key)
   - Compares computed signature with X-Signature header
   - Checks 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>

## Implementation by Language

<CodeGroup>
  ```python Python theme={null}
  import hmac
  import hashlib
  import time

  def _create_signature(workspace_id: str, valid_until: str, secret_key: str) -> str:
      """Create HMAC signature for authentication"""
      payload = f"{workspace_id}{valid_until}"
      signature = hmac.new(
          secret_key.encode(),
          payload.encode(),
          hashlib.sha256
      ).hexdigest()
      return signature

  def _get_auth_headers(workspace_id: str, api_key_id: str, secret_key: str) -> dict:
      """Generate authentication headers with HMAC signature"""
      valid_until = str(int(time.time()) + 300)  # Valid for 5 minutes
      signature = _create_signature(workspace_id, valid_until, secret_key)

      return {
          "X-API-Key-ID": api_key_id,
          "X-Workspace-ID": workspace_id,
          "X-Valid-Until": valid_until,
          "X-Signature": signature,
          "Content-Type": "application/json"
      }
  ```

  ```typescript TypeScript/Node.js theme={null}
  import crypto from "crypto";

  class DashboardAPIService {
    private config: {
      apiKeyId: string;
      secretKey: string;
      workspaceId: string;
      baseUrl: string;
    };

    constructor(config: any) {
      this.config = config;
    }

    private createSignature(workspaceId: string, validUntil: string): string {
      const payload = `${workspaceId}${validUntil}`;
      return crypto
        .createHmac("sha256", this.config.secretKey)
        .update(payload)
        .digest("hex");
    }

    private getAuthHeaders(): Record<string, string> {
      const validUntil = String(Math.floor(Date.now() / 1000) + 300);
      const signature = this.createSignature(this.config.workspaceId, validUntil);

      return {
        "X-API-Key-ID": this.config.apiKeyId,
        "X-Workspace-ID": this.config.workspaceId,
        "X-Valid-Until": validUntil,
        "X-Signature": signature,
        "Content-Type": "application/json",
      };
    }
  }
  ```

  ```php PHP theme={null}
  <?php
  function createSignature($workspaceId, $validUntil, $secretKey) {
      $payload = $workspaceId . $validUntil;
      return hash_hmac('sha256', $payload, $secretKey);
  }

  function getAuthHeaders($workspaceId, $apiKeyId, $secretKey) {
      $validUntil = time() + 300; // Valid for 5 minutes
      $signature = createSignature($workspaceId, (string)$validUntil, $secretKey);

      return [
          'X-API-Key-ID' => $apiKeyId,
          'X-Workspace-ID' => $workspaceId,
          'X-Valid-Until' => (string)$validUntil,
          'X-Signature' => $signature,
          'Content-Type' => 'application/json'
      ];
  }
  ?>
  ```

  ```java Java theme={null}
  import javax.crypto.Mac;
  import javax.crypto.spec.SecretKeySpec;
  import java.nio.charset.StandardCharsets;
  import java.time.Instant;

  @Service
  public class DashboardAPIService {

      @Value("${dashboard.api.key}")
      private String apiKeyId;

      @Value("${dashboard.secret.key}")
      private String secretKey;

      private String createSignature(String workspaceId, String validUntil)
          throws Exception {
          String payload = workspaceId + validUntil;

          Mac hmac = Mac.getInstance("HmacSHA256");
          SecretKeySpec secretKeySpec = new SecretKeySpec(
              secretKey.getBytes(StandardCharsets.UTF_8),
              "HmacSHA256"
          );
          hmac.init(secretKeySpec);

          byte[] hash = hmac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
          return bytesToHex(hash);
      }

      private HttpHeaders getAuthHeaders(String workspaceId) throws Exception {
          long validUntil = Instant.now().getEpochSecond() + 300;
          String signature = createSignature(workspaceId, String.valueOf(validUntil));

          HttpHeaders headers = new HttpHeaders();
          headers.set("X-API-Key-ID", apiKeyId);
          headers.set("X-Workspace-ID", workspaceId);
          headers.set("X-Valid-Until", String.valueOf(validUntil));
          headers.set("X-Signature", signature);
          headers.setContentType(MediaType.APPLICATION_JSON);

          return headers;
      }

      private String bytesToHex(byte[] bytes) {
          StringBuilder result = new StringBuilder();
          for (byte b : bytes) {
              result.append(String.format("%02x", b));
          }
          return result.toString();
      }
  }
  ```

  ```csharp C# theme={null}
  using System;
  using System.Security.Cryptography;
  using System.Text;

  public class DashboardAPIService
  {
      private readonly string _apiKeyId;
      private readonly string _secretKey;
      private readonly string _baseUrl;

      public DashboardAPIService(string apiKeyId, string secretKey, string baseUrl)
      {
          _apiKeyId = apiKeyId;
          _secretKey = secretKey;
          _baseUrl = baseUrl;
      }

      private string CreateSignature(string workspaceId, string validUntil)
      {
          var payload = $"{workspaceId}{validUntil}";
          var keyBytes = Encoding.UTF8.GetBytes(_secretKey);
          var payloadBytes = Encoding.UTF8.GetBytes(payload);

          using (var hmac = new HMACSHA256(keyBytes))
          {
              var hash = hmac.ComputeHash(payloadBytes);
              return BitConverter.ToString(hash).Replace("-", "").ToLower();
          }
      }

      private Dictionary<string, string> GetAuthHeaders(string workspaceId)
      {
          var validUntil = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 300;
          var signature = CreateSignature(workspaceId, validUntil.ToString());

          return new Dictionary<string, string>
          {
              { "X-API-Key-ID", _apiKeyId },
              { "X-Workspace-ID", workspaceId },
              { "X-Valid-Until", validUntil.ToString() },
              { "X-Signature", signature },
              { "Content-Type", "application/json" }
          };
      }
  }
  ```

  ```go Go theme={null}
  package dashboard

  import (
      "crypto/hmac"
      "crypto/sha256"
      "encoding/hex"
      "fmt"
      "time"
  )

  type DashboardAPIService struct {
      APIKeyID  string
      SecretKey string
      BaseURL   string
  }

  func (s *DashboardAPIService) createSignature(workspaceID, validUntil string) string {
      payload := workspaceID + validUntil
      h := hmac.New(sha256.New, []byte(s.SecretKey))
      h.Write([]byte(payload))
      return hex.EncodeToString(h.Sum(nil))
  }

  func (s *DashboardAPIService) getAuthHeaders(workspaceID string) map[string]string {
      validUntil := fmt.Sprintf("%d", time.Now().Unix()+300)
      signature := s.createSignature(workspaceID, validUntil)

      return map[string]string{
          "X-API-Key-ID":   s.APIKeyID,
          "X-Workspace-ID": workspaceID,
          "X-Valid-Until":  validUntil,
          "X-Signature":    signature,
          "Content-Type":   "application/json",
      }
  }
  ```
</CodeGroup>

## Token Expiration

* **API requests**: 5 minutes validity(advisable) based on the example above (`valid_until = now + 300`)
* Expired tokens are automatically rejected

## Security Best Practices

<Warning>
  **Never hardcode API credentials:** - Always use environment variables or a
  secrets management system - The API endpoint URL should be treated as
  sensitive configuration - Store credentials securely and rotate them regularly
</Warning>

### Environment Configuration

Store your credentials securely:

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

## Next Steps

<CardGroup cols={2}>
  <Card title="Creating Dashboards" icon="table-columns" href="/documentation/multi-tenancy/creating-dashboards">
    Learn how to create and manage dashboards for your tenants
  </Card>

  <Card title="Iframe Tokens" icon="code" href="/documentation/multi-tenancy/iframe-tokens">
    Generate secure tokens for embedding dashboards
  </Card>
</CardGroup>
