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

# Security & authentication

> How ButterCMS Image API secures media. A guide to access control strategies, including server-side proxying and signed URLs.

## Overview

ButterCMS media assets are served through a global CDN with built-in security features. This guide covers how image URLs work, access control, and best practices for protecting your media.

## URL security model

### Random CDN slugs

Each media file in ButterCMS is assigned a unique, random slug that serves as its identifier:

```
https://cdn.buttercms.com/Cw5z1qzSq5qtVMCbUDwQ
                          └──────────────────┘
                          Random unique slug
```

**Security Properties:**

* Slugs are randomly generated by Filestack during upload
* They are not sequential or predictable
* Knowledge of one URL does not reveal others

### Public CDN access

By default, all media on the ButterCMS CDN is publicly accessible:

* **No authentication required** to access images
* Images can be embedded anywhere (websites, emails, apps)
* Search engines can index images if linked publicly

<Info>
  This model follows the same security pattern as other headless CMS platforms—media URLs are designed to be publicly embeddable.
</Info>

## Access control strategies

### Keep URLs private

If you need to control who sees your images:

1. **Server-side rendering**: Fetch images server-side and serve through your authenticated endpoints
2. **Proxy through your app**: Create authenticated routes that proxy CDN requests
3. **Short-lived URLs**: Generate URLs only when needed, don't store them long-term

### Proxy implementation

```javascript theme={null}
// Server-side proxy for protected images
const express = require('express');
const axios = require('axios');
const app = express();

// Middleware to check user authentication
function requireAuth(req, res, next) {
  if (!req.user) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  next();
}

// Proxy endpoint for protected images
app.get('/api/images/:fileId', requireAuth, async (req, res) => {
  try {
    const response = await axios.get(
      `https://cdn.buttercms.com/${req.params.fileId}`,
      { responseType: 'stream' }
    );

    res.setHeader('Content-Type', response.headers['content-type']);
    response.data.pipe(res);
  } catch (error) {
    res.status(404).json({ error: 'Image not found' });
  }
});
```

### Signed URLs pattern

<Note>
  Signed URLs are not a built-in ButterCMS feature. The following is an example pattern you can implement in your own backend to control access through a proxy.
</Note>

Generate time-limited, signed URLs for sensitive content:

```javascript theme={null}
const crypto = require('crypto');

function generateSignedUrl(fileId, expiresIn = 3600) {
  const expires = Math.floor(Date.now() / 1000) + expiresIn;
  const signature = crypto
    .createHmac('sha256', process.env.URL_SIGNING_SECRET)
    .update(`${fileId}:${expires}`)
    .digest('hex');

  return `/api/images/${fileId}?expires=${expires}&signature=${signature}`;
}

// Verification middleware
function verifySignature(req, res, next) {
  const { fileId } = req.params;
  const { expires, signature } = req.query;

  if (Date.now() / 1000 > expires) {
    return res.status(403).json({ error: 'URL expired' });
  }

  const expectedSignature = crypto
    .createHmac('sha256', process.env.URL_SIGNING_SECRET)
    .update(`${fileId}:${expires}`)
    .digest('hex');

  if (signature !== expectedSignature) {
    return res.status(403).json({ error: 'Invalid signature' });
  }

  next();
}
```

## HTTPS enforcement

All ButterCMS CDN URLs use HTTPS:

```
✅ https://cdn.buttercms.com/abc123
❌ http://cdn.buttercms.com/abc123  (automatically redirects to HTTPS)
```

**Benefits:**

* Encrypted data transmission
* Protection against man-in-the-middle attacks
* Required for modern security headers (HSTS, CSP)

## Content security policy

Configure your CSP headers to allow ButterCMS images:

```
Content-Security-Policy: img-src 'self' https://cdn.buttercms.com;
```

### With inline styles (for background images)

```
Content-Security-Policy: img-src 'self' https://cdn.buttercms.com; style-src 'self' 'unsafe-inline';
```

### Next.js configuration

```javascript theme={null}
// next.config.js
module.exports = {
  images: {
    domains: ['cdn.buttercms.com'],
  },
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: "img-src 'self' https://cdn.buttercms.com;"
          }
        ]
      }
    ];
  }
};
```

## Transformation security

Image transformations don't require additional authentication:

```
https://cdn.buttercms.com/resize=width:800/abc123
```

**Considerations:**

* Anyone with the file ID can apply any transformation
* Transformations are cached at the CDN edge
* No additional cost for transformations

### Rate limiting transformations

If you're concerned about transformation abuse:

1. **Proxy transformations**: Route through your server with rate limiting
2. **Cache aggressively**: Use CDN caching for transformed images
3. **Monitor usage**: Track transformation requests in your analytics

## Hotlink protection

To prevent other sites from embedding your images:

### Server-side referrer checking

```javascript theme={null}
function checkReferrer(req, res, next) {
  const referrer = req.headers.referer || req.headers.referrer;
  const allowedDomains = ['yourdomain.com', 'yourapp.com'];

  if (referrer) {
    const referrerHost = new URL(referrer).hostname;
    if (!allowedDomains.some(domain => referrerHost.endsWith(domain))) {
      return res.status(403).json({ error: 'Hotlinking not allowed' });
    }
  }

  next();
}
```

### Nginx configuration

```nginx theme={null}
location /api/images/ {
    valid_referers none blocked yourdomain.com *.yourdomain.com;
    if ($invalid_referer) {
        return 403;
    }
    # Proxy to CDN...
}
```

## Best practices

<CardGroup cols={2}>
  <Card title="Don't Expose File IDs" icon="eye-slash">
    Avoid exposing CDN file IDs in client-side code for sensitive content.
  </Card>

  <Card title="Use HTTPS Always" icon="lock">
    Ensure all image references use HTTPS URLs.
  </Card>

  <Card title="Implement CSP" icon="shield-halved">
    Configure Content Security Policy headers for your domain.
  </Card>

  <Card title="Monitor Access" icon="chart-line">
    Track image access patterns for unusual activity.
  </Card>
</CardGroup>

## API token security

Your ButterCMS API tokens (Read/Write) are separate from image access:

| Token Type  | Image Access                | Use Case                  |
| ----------- | --------------------------- | ------------------------- |
| Read Token  | Not required for CDN images | Fetching content metadata |
| Write Token | Not required for CDN images | Creating/updating content |

<Warning>
  Never expose your Write API Token in client-side code. It can be used to modify or delete content.
</Warning>

## Sensitive content guidelines

For truly sensitive media:

1. **Don't upload to ButterCMS**: Use a dedicated secure storage solution
2. **Encrypt before upload**: Encrypt files client-side if you must store them
3. **Access control**: Implement server-side access control with signed URLs
4. **Audit logging**: Track all access to sensitive media

## Next steps

<CardGroup cols={2}>
  <Card title="Image Transformations" icon="wand-magic-sparkles" href="../image-api/image-transformations">
    Transform images via URL
  </Card>

  <Card title="Responsive Images" icon="mobile" href="../image-api/responsive-images">
    Optimize for devices
  </Card>

  <Card title="Authentication" icon="key" href="../concepts/authentication-api-tokens">
    API authentication
  </Card>

  <Card title="Media Library" icon="photo-film" href="/create-content/media-management/media-library-overview">
    Managing media
  </Card>
</CardGroup>
