AuraImage

Upload Flow

Edge Proxy Pattern via Cloudflare Workers — your Secret Key never leaves your server.

AuraImage uses the Edge Proxy Pattern via Cloudflare Workers. Your Secret Key never leaves your server; all upload requests are validated at the edge before a single byte reaches storage.

Architecture Overview

[Your Backend]  →  Generate HMAC Signature

[Your Frontend]  →  POST file + signature  →  [Cloudflare Worker]

                                             Pre-flight validation
                                             (magic bytes, size, HMAC)

                                             [Cloudflare R2]  ←  stored

                                             Returns final URL  →  Your app

Step 1 — Generate a Signature (Server-Side)

Your backend calls signUpload() using your Secret Key. This produces a short-lived HMAC token.

import { AuraImage } from '@auraimage/sdk';

const aura = new AuraImage({ secretKey: process.env.AURA_SECRET_KEY });

// POST /api/get-upload-signature
export async function POST() {
  const signature = aura.signUpload({
    slug: process.env.AURA_SLUG,
    maxSize: '5mb',                    // enforced at the edge
    allowedTypes: ['image/*'],         // magic-byte validated
    expires: Date.now() + 3_600_000,   // 1 hour
  });

  return Response.json({ signature });
}

The signature encodes constraints. An attacker who intercepts it cannot upload a larger file or a non-image format — the Worker enforces these server-side.

Step 2 — Upload from the Client

// 1. Fetch a signature from your own backend
const { signature } = await fetch('/api/get-upload-signature').then(r => r.json());

// 2. Build the form data
const formData = new FormData();
formData.append('file', file);                  // the image File object
formData.append('filename', 'hero-photo.jpg');  // optional; improves SEO

// 3. Upload to the AuraImage edge
const response = await fetch('https://api.auraimage.ai/v1/upload', {
  method: 'POST',
  headers: { 'X-Aura-Signature': signature },
  body: formData,
});

const { url, blurhash, width, height } = await response.json();

Upload Response

{
  "url": "https://auraimage.ai/narek/hero-photo.jpg",
  "blurhash": "LHF$Vb00~q~q9aM{RjxuIURjWBof",
  "width": 2400,
  "height": 1600,
  "format": "jxl",
  "size": 184320
}

Edge Validation

The Cloudflare Worker runs these checks before the upload hits R2:

CheckDescription
HMAC signatureVerifies the request was authorized by your Secret Key
ExpiryRejects signatures older than expires timestamp
Content-LengthEnforces the maxSize constraint from the signature
Magic BytesReads the first 12 bytes of the file to confirm it is actually an image
Domain WhitelistBlocks upload requests from origins not in your dashboard's allowed list
Rate LimitingPer-account request throttling to prevent "wallet-drain" attacks

HEIC / RAW Input Handling

AuraImage accepts all common input formats, including:

  • HEIC (iPhone default) — converted to JXL/AVIF for web delivery; original kept in R2
  • PNG, BMP, TIFF — converted on ingest; originals retained
  • RAW (CR3, ARW, NEF) — supported on Pro tier and above

The stored master is a lossless JXL recompression of the original, saving ~20% space with no quality loss.

BlurHash Generation

AuraImage automatically generates a BlurHash string for every uploaded image. This is returned in the upload response and is used by the <AuraImage /> component for instant placeholder display before the real image loads.

You can also request it separately:

GET https://api.auraimage.ai/v1/blurhash/narek/hero-photo.jpg

Programmatic Upload (CLI / MCP)

The aura CLI and MCP server can upload entire directories:

# Upload all images in /public/assets and print the new URLs
aura upload ./public/assets --slug narek

The MCP migrate_assets tool does this automatically and rewrites your JSX code in the same step. See AI Integration.